diff --git a/.editorconfig b/.editorconfig index 48df1807b..9ae4af12f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,90 +1,124 @@ + # see https://github.com/CppCXY/EmmyLuaCodeStyle [*.lua] -# [basic code reformat option] +# [basic] + # optional space/tab indent_style = space # if indent_style is space, this is valid indent_size = 4 # if indent_style is tab, this is valid tab_width = 4 -# only support number -continuation_indent_size = 4 -# if true, continuation_indent_size for local or assign statement is invalid -# however, if the expression list has cross row expression, it will not be aligned to the first expression -local_assign_continuation_align_to_first_expression = false -# function call expression's args will align to first arg -# however, if the args has cross row arg, it will not be aligned to the first arg -align_call_args = false -# if true, format like this "print( "123", 456 )" -keep_one_space_between_call_args_and_parentheses = false -# if true, all function define params will align to first param -align_function_define_params = true -# if true, format like this "local t = { 1, 2, 3 }" -keep_one_space_between_table_and_bracket = true -# if indent_style is tab, this option is invalid -align_table_field_to_first_field = false -# if true, ormat like this "local t = 1" -keep_one_space_between_namedef_and_attribute = false -# continous line distance -max_continuous_line_distance = 1 -# if true, iff any one of the consecutive rows meets the condition of aligning to the equal sign, -# the consecutive rows will be aligned to the equal sign -weak_alignment_rule = true -# see document for detail -continuous_assign_statement_align_to_equal_sign = true -# see document for detail -continuous_assign_table_field_align_to_equal_sign = true -# if true, the label loses its current indentation -label_no_indent = false -# if true, there will be no indentation in the do statement -do_statement_no_indent = false -# if true, the conditional expression of the if statement will not be a continuation line indent -if_condition_no_continuation_indent = false - - -# optional crlf/lf -end_of_line = auto -detect_end_of_line = true - -# [line layout] -# The following configuration supports three expressions -# minLine:${n} -# keepLine -# KeepLine:${n} - -keep_line_after_if_statement = minLine:0 -keep_line_after_do_statement = minLine:0 -keep_line_after_while_statement = minLine:0 -keep_line_after_repeat_statement = minLine:0 -keep_line_after_for_statement = minLine:0 -keep_line_after_local_or_assign_statement = keepLine -keep_line_after_function_define_statement = keepLine:1 - -# [diagnostic] -# the following is code diagnostic options -enable_check_codestyle = true -# this mean utf8 length +# none/single/double +quote_style = none + +continuation_indent = 4 + +# this mean utf8 length , if this is 'unset' then the line width is no longer checked +# this option decides when to chopdown the code max_line_length = 120 -# this will check text end with new line(format always end with new line) + +#optional keep/never/always/smart +trailing_table_separator = keep + +# keep/remove/remove_table_only/remove_string_only +call_arg_parentheses = keep + +detect_end_of_line = false + +# this will check text end with new line insert_final_newline = true -# [name style check] -enable_name_style_check = true -# the following is name style check rule -# base option off/camel_case/snake_case/upper_snake_case/pascal_case/same(filename/first_param/'', snake_case/pascal_case/camel_case) -# all option can use '|' represent or -# for example: -# snake_case | upper_snake_case -# same(first_param, snake_case) -# same('m') -local_name_define_style = camel_case|upper_snake_case -function_param_name_style = camel_case -function_name_define_style = camel_case -local_function_name_define_style = camel_case -table_field_name_define_style = camel_case|pascal_case -global_variable_name_define_style = camel_case|upper_snake_case -module_name_define_style = camel_case -require_module_name_style = camel_case -class_name_define_style = camel_case -table_append_expression_no_space = true -if_condition_align_with_each_other = true +# [space] +space_around_table_field_list = true + +space_before_attribute = true + +space_before_function_open_parenthesis = false + +space_before_function_call_open_parenthesis = false + +space_before_closure_open_parenthesis = true + +space_before_function_call_single_arg = true + +space_before_open_square_bracket = false + +space_inside_function_call_parentheses = false + +space_inside_function_param_list_parentheses = false + +space_inside_square_brackets = false + +# like t[#t+1] = 1 +space_around_table_append_operator = true + +ignore_spaces_inside_function_call = false + +space_before_inline_comment = 1 + +# [operator space] +space_around_math_operator = true + +space_after_comma = true + +space_after_comma_in_for_statement = true + +space_around_concat_operator = true + +# [align] + +align_call_args = false + +align_function_params = true + +align_continuous_assign_statement = true + +align_continuous_rect_table_field = true + +align_if_branch = true + +align_array_table = true + +# [indent] + +never_indent_before_if_condition = false + +never_indent_comment_on_if_branch = false + +# [line space] + +# The following configuration supports four expressions +# keep +# fixed(n) +# min(n) +# max(n) +# for eg. min(2) + +line_space_after_if_statement = keep + +line_space_after_do_statement = keep + +line_space_after_while_statement = keep + +line_space_after_repeat_statement = keep + +line_space_after_for_statement = keep + +line_space_after_local_or_assign_statement = keep + +line_space_after_function_statement = fixed(2) + +line_space_after_expression_statement = keep + +line_space_after_comment = keep + +# [line break] +break_all_list_when_line_exceed = false + +auto_collapse_lines = false + +# [preference] +ignore_space_after_colon = true + +remove_call_expression_list_finish_comma = false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0bb446b1b..07e672648 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ["https://paypal.me/sumneko", "https://github.com/sumneko/lua-language-server/issues/484"] +custom: ["https://paypal.me/sumneko", "https://github.com/LuaLS/lua-language-server/issues/484"] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 0451389e5..8eb237d37 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -88,7 +88,7 @@ body: label: Log File description: > Please provide your log file. Refer to the wiki to find your log file: - https://github.com/sumneko/lua-language-server/wiki/FAQ#where-can-i-find-the-log-file + https://luals.github.io/wiki/faq#where-can-i-find-the-log-file - type: markdown attributes: value: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7412228a..66640e50f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,8 @@ name: build +permissions: + contents: write + on: push: branches: @@ -20,25 +23,60 @@ jobs: fail-fast: false matrix: include: - - { os: ubuntu-18.04, target: linux, platform: linux-x64 } - - { os: ubuntu-18.04, target: linux, platform: linux-arm64 } + - { os: ubuntu-22.04, target: linux, platform: linux-x64, container: 'alpine:latest', libc: musl } + - { os: ubuntu-20.04, target: linux, platform: linux-x64, container: 'ubuntu:18.04' } + - { os: ubuntu-20.04, target: linux, platform: linux-arm64, container: 'ubuntu:18.04' } - { 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 }} + container: + image: ${{ matrix.container }} steps: + - name: Prepare container + if: ${{ matrix.target == 'linux' && matrix.libc != 'musl' }} + run: | + apt-get update + apt-get install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test # For gcc-9 and g++-9 + add-apt-repository -y ppa:git-core/ppa # For git>=2.18. + apt-get update + apt-get install -y sudo git gcc-9 g++-9 + - name: Install aarch64-linux-gnu - if: ${{ matrix.platform == 'linux-arm64' }} + if: ${{ matrix.platform == 'linux-arm64' && matrix.libc != 'musl' }} + run: | + apt-get update + apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + + - name: Prepare container env + if: ${{ matrix.target == 'linux' && matrix.libc != 'musl' }} + run: | + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100 + + - name: Prepare container for musl + if: ${{ matrix.target == 'linux' && matrix.libc == 'musl' }} run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + apk update + apk add git ninja bash build-base nodejs linux-headers - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actboy168/setup-luamake@master - - run: luamake -platform ${{ matrix.platform }} + + - name: Build for others step-1 + if: ${{ matrix.libc != 'musl' }} + uses: actboy168/setup-luamake@master + + - name: Build for others step-2 + if: ${{ matrix.libc != 'musl' }} + run: luamake -platform ${{ matrix.platform }} + + - name: Build for musl + if: ${{ matrix.target == 'linux' && matrix.libc == 'musl' }} + run: ./make.sh - name: Setting up workflow variables id: vars @@ -60,6 +98,9 @@ jobs: # Package name w/ version PKG_BASENAME="${{ env.PROJECT }}-${PKG_VERSION}-${{ matrix.platform }}" + if [[ "${{ matrix.libc }}" = musl ]]; then + PKG_BASENAME="${PKG_BASENAME}-${{matrix.libc}}" + fi # Full name of the tarball asset PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}" @@ -67,13 +108,13 @@ jobs: # Staging area for tarballs PKG_STAGING="ci_staging/$PKG_BASENAME" - echo ::set-output name=PKG_VERSION::${PKG_VERSION} - echo ::set-output name=PKG_BASENAME::${PKG_BASENAME} - echo ::set-output name=PKG_NAME::${PKG_NAME} - echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}" - echo ::set-output name=PKG_STAGING::${PKG_STAGING} - - - uses: actions/upload-artifact@v2 + echo PKG_VERSION=${PKG_VERSION} >> $GITHUB_OUTPUT + echo PKG_BASENAME=${PKG_BASENAME} >> $GITHUB_OUTPUT + echo PKG_NAME=${PKG_NAME} >> $GITHUB_OUTPUT + echo PKG_PATH="${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT + echo PKG_STAGING=${PKG_STAGING} >> $GITHUB_OUTPUT + + - uses: actions/upload-artifact@v3 with: name: ${{ steps.vars.outputs.PKG_BASENAME }} path: | @@ -118,9 +159,9 @@ jobs: PKG_SUBMOD_NAME="${{ env.PROJECT }}-${{ steps.vars.outputs.PKG_VERSION }}-submodules.zip" PKG_SUBMOD_PATH="${STAGING}/$PKG_SUBMOD_NAME" - zip -r $PKG_SUBMOD_PATH ./ -x "*.git*" -x "*.vscode*" -x "build*" -x "${{ env.BIN_DIR }}*" -x "${STAGING}*" + zip -r $PKG_SUBMOD_PATH ./ -x "*.git*" -x "*.vscode*" -x "build*" -x "${{ env.BIN_DIR }}*" -x "${STAGING}*" -x "3rd/json.lua*" -x "log*" -x "ci_staging*" - echo ::set-output name=PKG_SUBMOD_PATH::${PKG_SUBMOD_PATH} + echo PKG_SUBMOD_PATH=${PKG_SUBMOD_PATH} >> $GITHUB_OUTPUT - name: Publish release assets uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..e8ff3632d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: test +on: [ push, pull_request ] +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-20.04, platform: linux-x64 } + - { os: macos-14, platform: darwin-arm64 } + - { os: windows-latest, platform: win32-x64 } + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actboy168/setup-luamake@master + - run: luamake -platform ${{ matrix.platform }} diff --git a/.gitignore b/.gitignore index b88e4489d..4d868571c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,5 @@ /meta/* !/meta/template !/meta/3rd -/bin-Windows -/bin-Linux -/bin-macOS -/bin +!/meta/whimsical +/bin* diff --git a/.gitmodules b/.gitmodules index 1dfe374c8..32d827327 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,48 @@ [submodule "3rd/EmmyLuaCodeStyle"] path = 3rd/EmmyLuaCodeStyle url = https://github.com/CppCXY/EmmyLuaCodeStyle +[submodule "3rd/json.lua"] + path = 3rd/json.lua + url = https://github.com/actboy168/json.lua +[submodule "meta/3rd/OpenResty"] + path = meta/3rd/OpenResty + url = https://github.com/LuaCATS/openresty.git +[submodule "meta/3rd/bee"] + path = meta/3rd/bee + url = https://github.com/LuaCATS/bee.git +[submodule "meta/3rd/busted"] + path = meta/3rd/busted + url = https://github.com/LuaCATS/busted.git +[submodule "meta/3rd/Cocos4.0"] + path = meta/3rd/Cocos4.0 + url = https://github.com/LuaCATS/cocos4.0.git +[submodule "meta/3rd/Defold"] + path = meta/3rd/Defold + url = https://github.com/LuaCATS/defold.git +[submodule "meta/3rd/Jass"] + path = meta/3rd/Jass + url = https://github.com/LuaCATS/jass.git +[submodule "meta/3rd/lfs"] + path = meta/3rd/lfs + url = https://github.com/LuaCATS/luafilesystem.git +[submodule "meta/3rd/love2d"] + path = meta/3rd/love2d + url = https://github.com/LuaCATS/love2d.git +[submodule "meta/3rd/lovr"] + path = meta/3rd/lovr + url = https://github.com/LuaCATS/lovr.git +[submodule "meta/3rd/luaecs"] + path = meta/3rd/luaecs + url = https://github.com/LuaCATS/luaecs.git +[submodule "meta/3rd/luassert"] + path = meta/3rd/luassert + url = https://github.com/LuaCATS/luassert.git +[submodule "meta/3rd/skynet"] + path = meta/3rd/skynet + url = https://github.com/LuaCATS/skynet.git +[submodule "meta/3rd/ffi-reflect"] + path = meta/3rd/ffi-reflect + url = https://github.com/LuaCATS/ffi-reflect.git +[submodule "meta/3rd/luv"] + path = meta/3rd/luv + url = https://github.com/LuaCATS/luv.git diff --git a/.luarc.json b/.luarc.json index 64e09c460..fc02379f9 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,7 +1,10 @@ { "diagnostics": { "disable": [ - "close-non-object" + "close-non-object", + "incomplete-signature-doc", + "missing-global-doc", + "missing-local-export-doc" ], "groupFileStatus": { "ambiguity": "Any", @@ -41,7 +44,17 @@ ], "checkThirdParty": false }, + "typeFormat": { + "config": { + "format_line": "false" + } + }, "type": { "castNumberToInteger": false + }, + "doc": { + "privateName": [ + "_*" + ] } } diff --git a/.vscode/launch.json b/.vscode/launch.json index f43ee53c8..c90d73074 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,12 +9,10 @@ "stopOnEntry": false, "program": "${workspaceRoot}/test.lua", "luaexe": "${workspaceFolder}/bin/lua-language-server", - "cpath": null, - "arg": [ - ], - "luaVersion": "5.4", + "luaVersion": "lua54", "sourceCoding": "utf8", - "console": "internalConsole", + "console": "integratedTerminal", + "internalConsoleOptions": "openOnSessionStart", "outputCapture": [ "print", "stderr", @@ -25,7 +23,7 @@ "type": "lua", "request": "attach", "stopOnEntry": false, - "address": "127.0.0.1:11427", + "address": "127.0.0.1:11428", "outputCapture": [ ], "sourceFormat": "string", @@ -52,9 +50,11 @@ "luaexe": "${workspaceFolder}/bin/lua-language-server", "program": "${workspaceRoot}/tools/build-3rd-meta.lua", "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", + "console": "integratedTerminal", + "internalConsoleOptions": "openOnSessionStart", "arg": [ ], - "luaVersion": "latest", + "luaVersion": "lua-latest", "sourceCoding": "utf8", "outputCapture": [ "print", @@ -62,18 +62,18 @@ ], }, { - "name": "๐Ÿ–cli-check", + "name": "๐Ÿ“locale", "type": "lua", "request": "launch", "stopOnEntry": false, - "program": "${workspaceRoot}/main.lua", "luaexe": "${workspaceFolder}/bin/lua-language-server", - "cpath": null, + "program": "${workspaceRoot}/tools/locale.lua", + "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", + "console": "integratedTerminal", + "internalConsoleOptions": "openOnSessionStart", "arg": [ - "--check", - "${workspaceRoot}", ], - "luaVersion": "5.4", + "luaVersion": "lua-latest", "sourceCoding": "utf8", "outputCapture": [ "print", @@ -81,16 +81,18 @@ ], }, { - "name": "๐Ÿ“locale", + "name": "๐Ÿ€„build-doc", "type": "lua", "request": "launch", "stopOnEntry": false, "luaexe": "${workspaceFolder}/bin/lua-language-server", - "program": "${workspaceRoot}/tools/locale.lua", + "program": "${workspaceRoot}/tools/build-doc.lua", "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", + "console": "integratedTerminal", + "internalConsoleOptions": "openOnSessionStart", "arg": [ ], - "luaVersion": "latest", + "luaVersion": "lua-latest", "sourceCoding": "utf8", "outputCapture": [ "print", @@ -98,16 +100,37 @@ ], }, { - "name": "๐Ÿ€„build-doc", + "name": "๐Ÿ–cli-check", "type": "lua", "request": "launch", "stopOnEntry": false, + "program": "${workspaceRoot}/main.lua", "luaexe": "${workspaceFolder}/bin/lua-language-server", - "program": "${workspaceRoot}/tools/build-doc.lua", - "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", + "cpath": null, "arg": [ + "--check", + "${workspaceRoot}", + ], + "luaVersion": "lua-latest", + "sourceCoding": "utf8", + "outputCapture": [ + "print", + "stderr", + ], + }, + { + "name": "๐Ÿคcli-doc", + "type": "lua", + "request": "launch", + "stopOnEntry": false, + "program": "${workspaceRoot}/main.lua", + "luaexe": "${workspaceFolder}/bin/lua-language-server", + "cpath": null, + "arg": [ + "--doc", + "${workspaceRoot}", ], - "luaVersion": "latest", + "luaVersion": "lua-latest", "sourceCoding": "utf8", "outputCapture": [ "print", diff --git a/.vscode/settings.json b/.vscode/settings.json index 006ee248d..d177c307d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,11 @@ // Just some comment { "Lua.misc.parameters": [ - "--preview", + //"--preview", "--develop=true", "--dbgport=11414", "--loglevel=trace", - ] + "--shownode", + //"--lazy", + ], } diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle index eda85bbac..783f269a7 160000 --- a/3rd/EmmyLuaCodeStyle +++ b/3rd/EmmyLuaCodeStyle @@ -1 +1 @@ -Subproject commit eda85bbaceee4f98cac87ef77870b236ac3b0997 +Subproject commit 783f269a70e7b794caab4a8b4073d08c4aae6ccb diff --git a/3rd/bee.lua b/3rd/bee.lua index 4b7e3d724..1f01891c5 160000 --- a/3rd/bee.lua +++ b/3rd/bee.lua @@ -1 +1 @@ -Subproject commit 4b7e3d724d9d64836b0823dfc7ccc1a4713b6421 +Subproject commit 1f01891c5dfcb0a740e2b573eeefd4ea4efd9a37 diff --git a/3rd/json.lua b/3rd/json.lua new file mode 160000 index 000000000..21c9584d3 --- /dev/null +++ b/3rd/json.lua @@ -0,0 +1 @@ +Subproject commit 21c9584d30fa36c542c98b6b1410393318583712 diff --git a/3rd/love-api b/3rd/love-api index 69e7e0156..728ba001f 160000 --- a/3rd/love-api +++ b/3rd/love-api @@ -1 +1 @@ -Subproject commit 69e7e015698c0834798a0b9ffeb3a98786b60f1d +Subproject commit 728ba001f3398fd11b0a3909b919a7caf3e329a4 diff --git a/3rd/lovr-api b/3rd/lovr-api index 5bc82dc8b..e89c753e1 160000 --- a/3rd/lovr-api +++ b/3rd/lovr-api @@ -1 +1 @@ -Subproject commit 5bc82dc8b3a05207d14e8ef7ab6334c672845fb6 +Subproject commit e89c753e1c2849b7533481fcf058095f8e050b9f diff --git a/3rd/lpeglabel b/3rd/lpeglabel index ed1838602..912b0b9e8 160000 --- a/3rd/lpeglabel +++ b/3rd/lpeglabel @@ -1 +1 @@ -Subproject commit ed183860289664af0f3727307653c9bf2bccdc80 +Subproject commit 912b0b9e8641074408ffc2259e069b188e0c717b diff --git a/3rd/luamake b/3rd/luamake index 074888f8e..ffa2770d1 160000 --- a/3rd/luamake +++ b/3rd/luamake @@ -1 +1 @@ -Subproject commit 074888f8e5a47e109fc92257508d7b40ea7cceb8 +Subproject commit ffa2770d1261bf12af2cfed5551b55df85c089d9 diff --git a/README.md b/README.md index b51ae7db2..aa40ab379 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,63 @@ # lua-language-server -![build](https://github.com/sumneko/lua-language-server/workflows/build/badge.svg) -[![version](https://vsmarketplacebadge.apphb.com/version-short/sumneko.lua.svg)](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) -![installs](https://vsmarketplacebadge.apphb.com/installs-short/sumneko.lua.svg) -![downloads](https://vsmarketplacebadge.apphb.com/downloads-short/sumneko.lua.svg) -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/sumneko/lua-language-server.svg)](https://github.com/sumneko/lua-language-server/issues "Average time to resolve an issue") +![build](https://img.shields.io/github/actions/workflow/status/LuaLS/lua-language-server/.github%2Fworkflows%2Fbuild.yml) +![Version (including pre-releases)](https://img.shields.io/visual-studio-marketplace/v/sumneko.lua) +![Installs](https://img.shields.io/visual-studio-marketplace/i/sumneko.lua) +![Downloads](https://img.shields.io/visual-studio-marketplace/d/sumneko.lua) ***Lua development just got a whole lot better*** ๐Ÿง  -The Lua language server provides various language features for Lua to make development easier and faster. With around half a million installs on Visual Studio Code, it is the most popular extension for Lua language support. +The Lua language server provides various language features for Lua to make development easier and faster. With nearly a million installs in Visual Studio Code, it is the most popular extension for Lua language support. + +[See our website for more info](https://luals.github.io). ## Features -- ๐Ÿ“„ Over 20 supported [annotations](https://github.com/sumneko/lua-language-server/wiki/Annotations) for documenting your code +- โš™๏ธ Supports `Lua 5.4`, `Lua 5.3`, `Lua 5.2`, `Lua 5.1`, and `LuaJIT` +- ๐Ÿ“„ Over 20 supported [annotations](https://luals.github.io/wiki/annotations/) for documenting your code - โ†ช Go to definition -- ๐Ÿฆบ Dynamic [type checking](https://github.com/sumneko/lua-language-server/wiki/Type-Checking) +- ๐Ÿฆบ Dynamic [type checking](https://luals.github.io/wiki/type-checking/) - ๐Ÿ” Find references -- โš ๏ธ [Diagnostics/Warnings](https://github.com/sumneko/lua-language-server/wiki/Diagnostics) -- ๐Ÿ•ต๏ธ [Syntax checking](https://github.com/sumneko/lua-language-server/wiki/Syntax-Errors) +- โš ๏ธ [Diagnostics/Warnings](https://luals.github.io/wiki/diagnostics/) +- ๐Ÿ•ต๏ธ [Syntax checking](https://luals.github.io/wiki/syntax-errors/) - ๐Ÿ“ Element renaming - ๐Ÿ—จ๏ธ Hover to view details on variables, functions, and more - ๐Ÿ–Š๏ธ Autocompletion -- ๐Ÿ“š Support for [libraries](https://github.com/sumneko/lua-language-server/wiki/Libraries) -- ๐Ÿ’… [Code formatting](https://github.com/sumneko/lua-language-server/wiki/Formatter) -- ๐Ÿ’ฌ [Spell checking](https://github.com/sumneko/lua-language-server/wiki/Formatter) -- ๐Ÿ› ๏ธ Custom [plugins](https://github.com/sumneko/lua-language-server/wiki/Plugins) +- ๐Ÿ“š Support for [libraries](https://luals.github.io/wiki/settings/#workspacelibrary) +- ๐Ÿ’… [Code formatting](https://luals.github.io/wiki/formatter/) +- ๐Ÿ’ฌ [Spell checking](https://luals.github.io/wiki/diagnostics/#spell-check) +- ๐Ÿ› ๏ธ Custom [plugins](https://luals.github.io/wiki/plugins/) +- ๐Ÿ“– [Documentation Generation](https://luals.github.io/wiki/export-docs/) ## Install +The language server can be installed for use in Visual Studio Code, NeoVim, and any [other clients](https://microsoft.github.io/language-server-protocol/implementors/tools/) that support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). -The language server can easily be installed for use in VS Code, but it can also be used by other clients using the command line. - -### Visual Studio Code -[![Install in VS Code](https://img.shields.io/badge/Install%20For-VS%20Code-blue?style=for-the-badge&logo=visualstudiocode "Install in VS Code")](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) - -The language server and Visual Studio Code client can be installed from [the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=sumneko.lua). +See [installation instructions on our website](https://luals.github.io/#install). -![](https://github.com/sumneko/vscode-lua/raw/master/images//Install%20In%20VSCode.gif) +[![Install in VS Code](https://img.shields.io/badge/VS%20Code-Install-blue?style=for-the-badge&logo=visualstudiocode "Install in VS Code")](https://luals.github.io/#vscode-install) +[![Install for NeoVim](https://img.shields.io/badge/NeoVim-Install-blue?style=for-the-badge&logo=neovim "Install for NeoVim")](https://luals.github.io/#neovim-install) +[![Other](https://img.shields.io/badge/Other-Install-blue?style=for-the-badge&logo=windowsterminal "Install for command line")](https://luals.github.io/#other-install) -### Command Line -[![Install for command line](https://img.shields.io/badge/Install%20For-Command%20Line-blue?style=for-the-badge&logo=windowsterminal "Install for command line")](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#command-line) +### Community Install Methods +The install methods below are maintained by community members. -Check the [wiki for a guide](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#command-line) to install the language server for use on the command line. This allows the language server to be used for NeoVim and other clients that follow the language server protocol. - -## Supported Lua Versions -| Version | Supported | -| :-----: | :------------: | -| Lua 5.1 | ![][checkmark] | -| Lua 5.2 | ![][checkmark] | -| Lua 5.3 | ![][checkmark] | -| Lua 5.4 | ![][checkmark] | -| LuaJIT | ![][checkmark] | +[asdf plugin](https://github.com/bellini666/asdf-lua-language-server) ## Links -- [Changelog](https://github.com/sumneko/lua-language-server/blob/master/changelog.md) -- [Wiki](https://github.com/sumneko/lua-language-server/wiki) -- [FAQ](https://github.com/sumneko/lua-language-server/wiki/FAQ) +- [Changelog](https://github.com/LuaLS/lua-language-server/blob/master/changelog.md) +- [Wiki](https://luals.github.io/wiki) +- [FAQ](https://luals.github.io/wiki/faq) - [Report an issue][issues] - [Suggest a feature][issues] -- [Discuss](https://github.com/sumneko/lua-language-server/discussions) +- [Discuss](https://github.com/LuaLS/lua-language-server/discussions) > If you find any mistakes, please [report it][issues] or open a [pull request][pulls] if you have a fix of your own โค๏ธ > > ๅฆ‚ๆžœไฝ ๅ‘็Žฐไบ†ไปปไฝ•้”™่ฏฏ๏ผŒ่ฏท[ๅ‘Š่ฏ‰ๆˆ‘][issues]ๆˆ–ไฝฟ็”จ[Pull Requests][pulls]ๆฅ็›ดๆŽฅไฟฎๅคใ€‚โค๏ธ -[issues]: https://github.com/sumneko/lua-language-server/issues -[pulls]: https://github.com/sumneko/lua-language-server/pulls +[issues]: https://github.com/LuaLS/lua-language-server/issues +[pulls]: https://github.com/LuaLS/lua-language-server/pulls ## Available Languages @@ -75,26 +67,16 @@ Check the [wiki for a guide](https://github.com/sumneko/lua-language-server/wiki - `pt-br` ๐Ÿ‡ง๐Ÿ‡ท -> โ„น Note: All translations are provided and collaborated on by the community. If you find an inappropriate or harmful translation, [please report it immediately](https://github.com/sumneko/lua-language-server/issues). - -Are you able to [provide a translation](https://github.com/sumneko/lua-language-server/wiki/Translations)? It would be greatly appreciated! +> **Note** +> All translations are provided and collaborated on by the community. If you find an inappropriate or harmful translation, [please report it immediately](https://github.com/LuaLS/lua-language-server/issues). -Thank you to [all contributors of translations](https://github.com/sumneko/lua-language-server/commits/master/locale)! +Are you able to [provide a translation](https://luals.github.io/wiki/translations)? It would be greatly appreciated! -[en-US]: https://github.com/sumneko/lua-language-server/tree/master/locale/en-us - -## Configuration -Configuration of the server can be done in a number of ways, which are explained more in-depth in the [wiki](https://github.com/sumneko/lua-language-server/wiki/Configuration-File). - -### Visual Studio Code -You can use the [settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor) or edit the [raw JSON file](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson). - -### Other -See the [configuration file wiki page](https://github.com/sumneko/lua-language-server/wiki/Configuration-File). +Thank you to [all contributors of translations](https://github.com/LuaLS/lua-language-server/commits/master/locale)! ## Privacy -This language server has **opt-in** telemetry that collects usage data and sends it to the development team to help improve the extension. Read our [privacy policy](https://github.com/sumneko/lua-language-server/wiki/Home#privacy) to learn more. +The language server had **opt-in** telemetry that collected usage data and sent it to the development team to help improve the extension. Read our [privacy policy](https://luals.github.io/privacy#language-server) to learn more. Telemetry was removed in `v3.6.5` and is no longer part of the language server. ## Contributors @@ -106,17 +88,14 @@ Software that the language server (or the development of it) uses: * [bee.lua](https://github.com/actboy168/bee.lua) * [luamake](https://github.com/actboy168/luamake) * [LPegLabel](https://github.com/sqmedeiros/lpeglabel) -* [LuaParser](https://github.com/sumneko/LuaParser) +* [LuaParser](https://github.com/LuaLS/LuaParser) * [ScreenToGif](https://github.com/NickeManarin/ScreenToGif) * [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node) * [lua.tmbundle](https://github.com/textmate/lua.tmbundle) * [EmmyLua](https://emmylua.github.io) -* [lua-glob](https://github.com/sumneko/lua-glob) -* [utility](https://github.com/sumneko/utility) +* [lua-glob](https://github.com/LuaLS/lua-glob) +* [utility](https://github.com/LuaLS/utility) * [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) - - -[checkmark]: https://gist.githubusercontent.com/carsakiller/362482775731de88cdafeeca9f6a392e/raw/cd3976e92a85aafe1f33e3f9fe3d0e0bd451902c/checkmark.svg diff --git a/changelog.md b/changelog.md index 18d8569a6..201103318 100644 --- a/changelog.md +++ b/changelog.md @@ -1,28 +1,592 @@ # changelog +## 3.7.5 +* `FIX` rename in library files + +## 3.7.4 +`2024-1-5` +* `FIX` rename to unicode with `Lua.runtime.unicodeName = true` + +## 3.7.3 +`2023-11-14` +* `FIX` can not infer arg type in some cases. + +## 3.7.2 +`2023-11-9` +* `FIX` [#2407] + +[#2407]: https://github.com/LuaLS/lua-language-server/issues/2407 + +## 3.7.1 +`2023-11-7` +* `FIX` [#2299] +* `FIX` [#2335] + +[#2299]: https://github.com/LuaLS/lua-language-server/issues/2299 +[#2335]: https://github.com/LuaLS/lua-language-server/issues/2335 + +## 3.7.0 +`2023-8-24` +* `NEW` support `---@type` and `--[[@as]]` for return statement +* `NEW` commandline parameter `--force-accept-workspace`: allowing the use of the root directory or home directory as the workspace +* `NEW` diagnostic: `inject-field` +* `NEW` `---@enum` supports attribute `key` + ```lua + ---@enum (key) AnimalType + local enum = { + Cat = 1, + Dog = 2, + } + + ---@param animal userdata + ---@param atp AnimalType + ---@return boolean + local function isAnimalType(animal, atp) + return API.isAnimalType(animal, enum[atp]) + end + + assert(isAnimalType(animal, 'Cat')) + ``` +* `NEW` `---@class` supports attribute `exact` + ```lua + ---@class (exact) Point + ---@field x number + ---@field y number + local m = {} + m.x = 1 -- OK + m.y = 2 -- OK + m.z = 3 -- Warning + ``` + +* `FIX` wrong hover and signature for method with varargs and overloads +* `FIX` [#2155] +* `FIX` [#2224] +* `FIX` [#2252] +* `FIX` [#2267] + +[#2155]: https://github.com/LuaLS/lua-language-server/issues/2155 +[#2224]: https://github.com/LuaLS/lua-language-server/issues/2224 +[#2252]: https://github.com/LuaLS/lua-language-server/issues/2252 +[#2267]: https://github.com/LuaLS/lua-language-server/issues/2267 + +## 3.6.25 +`2023-7-26` +* `FIX` [#2214] + +[#2214]: https://github.com/LuaLS/lua-language-server/issues/2214 + +## 3.6.24 +`2023-7-21` +* `NEW` diagnostic: `missing-fields` +* `FIX` shake of `codeLens` +* `FIX` [#2145] + +[#2145]: https://github.com/LuaLS/lua-language-server/issues/2145 + +## 3.6.23 +`2023-7-7` +* `CHG` signature: narrow by inputed literal + +## 3.6.22 +`2023-6-14` +* `FIX` [#2038] +* `FIX` [#2042] +* `FIX` [#2062] +* `FIX` [#2083] +* `FIX` [#2088] +* `FIX` [#2110] +* `FIX` [#2129] + +[#2038]: https://github.com/LuaLS/lua-language-server/issues/2038 +[#2042]: https://github.com/LuaLS/lua-language-server/issues/2042 +[#2062]: https://github.com/LuaLS/lua-language-server/issues/2062 +[#2083]: https://github.com/LuaLS/lua-language-server/issues/2083 +[#2088]: https://github.com/LuaLS/lua-language-server/issues/2088 +[#2110]: https://github.com/LuaLS/lua-language-server/issues/2110 +[#2129]: https://github.com/LuaLS/lua-language-server/issues/2129 + +## 3.6.21 +`2023-5-24` +* `FIX` disable ffi plugin + +## 3.6.20 +`2023-5-23` +* `NEW` support connecting by socket with `--socket=PORT` +* `FIX` [#2113] + +[#2113]: https://github.com/LuaLS/lua-language-server/issues/2113 + +## 3.6.19 +`2023-4-26` +* `FIX` commandline parameter `checklevel` may not work +* `FIX` [#2036] +* `FIX` [#2037] +* `FIX` [#2056] +* `FIX` [#2077] +* `FIX` [#2081] + +[#2036]: https://github.com/LuaLS/lua-language-server/issues/2036 +[#2037]: https://github.com/LuaLS/lua-language-server/issues/2037 +[#2056]: https://github.com/LuaLS/lua-language-server/issues/2056 +[#2077]: https://github.com/LuaLS/lua-language-server/issues/2077 +[#2081]: https://github.com/LuaLS/lua-language-server/issues/2081 + +## 3.6.18 +`2023-3-23` +* `FIX` [#1943] +* `FIX` [#1996] +* `FIX` [#2004] +* `FIX` [#2013] + +[#1943]: https://github.com/LuaLS/lua-language-server/issues/1943 +[#1996]: https://github.com/LuaLS/lua-language-server/issues/1996 +[#2004]: https://github.com/LuaLS/lua-language-server/issues/2004 +[#2013]: https://github.com/LuaLS/lua-language-server/issues/2013 + +## 3.6.17 +`2023-3-9` +* `CHG` export documents: export global variables +* `FIX` [#1715] +* `FIX` [#1753] +* `FIX` [#1914] +* `FIX` [#1922] +* `FIX` [#1924] +* `FIX` [#1928] +* `FIX` [#1945] +* `FIX` [#1955] +* `FIX` [#1978] + +[#1715]: https://github.com/LuaLS/lua-language-server/issues/1715 +[#1753]: https://github.com/LuaLS/lua-language-server/issues/1753 +[#1914]: https://github.com/LuaLS/lua-language-server/issues/1914 +[#1922]: https://github.com/LuaLS/lua-language-server/issues/1922 +[#1924]: https://github.com/LuaLS/lua-language-server/issues/1924 +[#1928]: https://github.com/LuaLS/lua-language-server/issues/1928 +[#1945]: https://github.com/LuaLS/lua-language-server/issues/1945 +[#1955]: https://github.com/LuaLS/lua-language-server/issues/1955 +[#1978]: https://github.com/LuaLS/lua-language-server/issues/1978 + +## 3.6.13 +`2023-3-2` +* `FIX` setting: `Lua.addonManager.enable` should be `true` by default +* `FIX` failed to publish to Windows + +## 3.6.12 +`2023-3-2` +* `NEW` [Addon Manager](https://github.com/LuaLS/lua-language-server/discussions/1607), try it with command `lua.addon_manager.open`. Thanks to [carsakiller](https://github.com/carsakiller)! + +## 3.6.11 +`2023-2-13` +* `CHG` completion: don't show loading process +* `FIX` [#1886] +* `FIX` [#1887] +* `FIX` [#1889] +* `FIX` [#1895] +* `FIX` [#1902] + +[#1886]: https://github.com/LuaLS/lua-language-server/issues/1886 +[#1887]: https://github.com/LuaLS/lua-language-server/issues/1887 +[#1889]: https://github.com/LuaLS/lua-language-server/issues/1889 +[#1895]: https://github.com/LuaLS/lua-language-server/issues/1895 +[#1902]: https://github.com/LuaLS/lua-language-server/issues/1902 + +## 3.6.10 +`2023-2-7` +* `FIX` [#1869] +* `FIX` [#1872] + +[#1869]: https://github.com/LuaLS/lua-language-server/issues/1869 +[#1872]: https://github.com/LuaLS/lua-language-server/issues/1872 + +## 3.6.9 +`2023-2-2` +* `FIX` [#1864] +* `FIX` [#1868] +* `FIX` [#1869] +* `FIX` [#1871] + +[#1864]: https://github.com/LuaLS/lua-language-server/issues/1864 +[#1868]: https://github.com/LuaLS/lua-language-server/issues/1868 +[#1869]: https://github.com/LuaLS/lua-language-server/issues/1869 +[#1871]: https://github.com/LuaLS/lua-language-server/issues/1871 + +## 3.6.8 +`2023-1-31` +* `NEW` command `lua.exportDocument` . VSCode will display this command in the right-click menu +* `CHG` setting `Lua.workspace.supportScheme` has been removed. All schemes are supported if the language id is `lua` +* `FIX` [#1831] +* `FIX` [#1838] +* `FIX` [#1841] +* `FIX` [#1851] +* `FIX` [#1855] +* `FIX` [#1857] + +[#1831]: https://github.com/LuaLS/lua-language-server/issues/1831 +[#1838]: https://github.com/LuaLS/lua-language-server/issues/1838 +[#1841]: https://github.com/LuaLS/lua-language-server/issues/1841 +[#1851]: https://github.com/LuaLS/lua-language-server/issues/1851 +[#1855]: https://github.com/LuaLS/lua-language-server/issues/1855 +[#1857]: https://github.com/LuaLS/lua-language-server/issues/1857 + +## 3.6.7 +`2023-1-20` +* `FIX` [#1810] +* `FIX` [#1829] + +[#1810]: https://github.com/LuaLS/lua-language-server/issues/1810 +[#1829]: https://github.com/LuaLS/lua-language-server/issues/1829 + +## 3.6.6 +`2023-1-17` +* `FIX` [#1825] +* `FIX` [#1826] + +[#1825]: https://github.com/LuaLS/lua-language-server/issues/1825 +[#1826]: https://github.com/LuaLS/lua-language-server/issues/1826 + +## 3.6.5 +`2023-1-16` +* `NEW` support casting global variables +* `NEW` code lens: this feature is disabled by default. +* `NEW` settings: + * `Lua.codeLens.enable`: Enable code lens. +* `CHG` improve memory usage for large libraries +* `CHG` definition: supports finding definitions for `@class` and `@alias`, since they may be defined multi times +* `CHG` rename: supports `@field` +* `CHG` improve patch for `.luarc.json` +* `CHG` `---@meta [name]`: once declared `name`, user can only require this file by declared name. meta file can not be required with name `_` +* `CHG` remove telemetry +* `FIX` [#831] +* `FIX` [#1729] +* `FIX` [#1737] +* `FIX` [#1751] +* `FIX` [#1767] +* `FIX` [#1796] +* `FIX` [#1805] +* `FIX` [#1808] +* `FIX` [#1811] +* `FIX` [#1824] + +[#831]: https://github.com/LuaLS/lua-language-server/issues/831 +[#1729]: https://github.com/LuaLS/lua-language-server/issues/1729 +[#1737]: https://github.com/LuaLS/lua-language-server/issues/1737 +[#1751]: https://github.com/LuaLS/lua-language-server/issues/1751 +[#1767]: https://github.com/LuaLS/lua-language-server/issues/1767 +[#1796]: https://github.com/LuaLS/lua-language-server/issues/1796 +[#1805]: https://github.com/LuaLS/lua-language-server/issues/1805 +[#1808]: https://github.com/LuaLS/lua-language-server/issues/1808 +[#1811]: https://github.com/LuaLS/lua-language-server/issues/1811 +[#1824]: https://github.com/LuaLS/lua-language-server/issues/1824 + +## 3.6.4 +`2022-11-29` +* `NEW` modify `require` after renaming files +* `FIX` circulation reference in process analysis + ```lua + ---@type number + local x + + ---@type number + local y + + x = y + + y = x --> Can not infer `y` before + ``` +* `FIX` [#1698] +* `FIX` [#1704] +* `FIX` [#1717] + +[#1698]: https://github.com/LuaLS/lua-language-server/issues/1698 +[#1704]: https://github.com/LuaLS/lua-language-server/issues/1704 +[#1717]: https://github.com/LuaLS/lua-language-server/issues/1717 + +## 3.6.3 +`2022-11-14` +* `FIX` [#1684] +* `FIX` [#1692] + +[#1684]: https://github.com/LuaLS/lua-language-server/issues/1684 +[#1692]: https://github.com/LuaLS/lua-language-server/issues/1692 + +## 3.6.2 +`2022-11-10` +* `FIX` incorrect type check for generic with nil +* `FIX` [#1676] +* `FIX` [#1677] +* `FIX` [#1679] +* `FIX` [#1680] + +[#1676]: https://github.com/LuaLS/lua-language-server/issues/1676 +[#1677]: https://github.com/LuaLS/lua-language-server/issues/1677 +[#1679]: https://github.com/LuaLS/lua-language-server/issues/1679 +[#1680]: https://github.com/LuaLS/lua-language-server/issues/1680 + +## 3.6.1 +`2022-11-8` +* `FIX` wrong diagnostics for `pcall` and `xpcall` +* `FIX` duplicate fields in table hover +* `FIX` description disapeared for overloaded function +* `FIX` [#1675] + +[#1675]: https://github.com/LuaLS/lua-language-server/issues/1675 + +## 3.6.0 +`2022-11-8` +* `NEW` supports `private`/`protected`/`public`/`package` + * mark in `doc.field` + ```lua + ---@class unit + ---@field private uuid integer + ``` + * mark with `---@private`, `---@protected`, `---@public` and `---@package` + ```lua + ---@class unit + local mt = {} + + ---@private + function mt:init() + end + + ---@protected + function mt:update() + end + ``` + * mark by settings `Lua.doc.privateName`, `Lua.doc.protectedName` and `Lua.doc.packageName` + ```lua + ---@class unit + ---@field _uuid integer --> treat as private when `Lua.doc.privateName` has `"_*"` + ``` +* `NEW` settings: + * `Lua.misc.executablePath`: [#1557] specify the executable path in VSCode + * `Lua.diagnostics.workspaceEvent`: [#1626] set the time to trigger workspace diagnostics. + * `Lua.doc.privateName`: treat matched fields as private + * `Lua.doc.protectedName`: treat matched fields as protected + * `Lua.doc.packageName`: treat matched fields as package +* `NEW` CLI `--doc [path]` to make docs. +server will generate `doc.json` and `doc.md` in `LOGPATH`. +`doc.md` is generated by `doc.json` by example code `script/cli/doc2md.lua`. +* `CHG` [#1558] detect multi libraries +* `CHG` [#1458] `semantic-tokens`: global variable is setted to `variable.global` + ```jsonc + // color global variables to red + "editor.semanticTokenColorCustomizations": { + "rules": { + "variable.global": "#ff0000" + } + } + ``` +* `CHG` [#1177] re-support for symlinks, users need to maintain the correctness of symlinks themselves +* `CHG` [#1561] infer definitions and types across chain expression + ```lua + ---@class myClass + local myClass = {} + + myClass.a.b.c.e.f.g = 1 + + ---@type myClass + local class + + print(class.a.b.c.e.f.g) --> inferred as integer + ``` +* `CHG` [#1582] the following diagnostics consider `overload` + * `missing-return` + * `missing-return-value` + * `redundant-return-value` + * `return-type-mismatch` +* `CHG` workspace-symbol: supports chain fields based on global variables and types. try `io.open` or `iolib.open` +* `CHG` [#1641] if a function only has varargs and has `---@overload`, the varargs will be ignored +* `CHG` [#1575] search definitions by first argument of `setmetatable` + ```lua + ---@class Object + local obj = setmetatable({ + initValue = 1, + }, mt) + + print(obj.initValue) --> `obj.initValue` is integer + ``` +* `CHG` [#1153] infer type by generic parameters or returns of function + ```lua + ---@generic T + ---@param f fun(x: T) + ---@return T[] + local function x(f) end + + ---@type fun(x: integer) + local cb + + local arr = x(cb) --> `arr` is inferred as `integer[]` + ``` +* `CHG` [#1201] infer parameter type by expected returned function of parent function + ```lua + ---@return fun(x: integer) + local function f() + return function (x) --> `x` is inferred as `integer` + end + end + ``` +* `CHG` [#1332] infer parameter type when function in table + ```lua + ---@class A + ---@field f fun(x: string) + + ---@type A + local t = { + f = function (x) end --> `x` is inferred as `string` + } + ``` +* `CHG` find reference: respect `includeDeclaration` (although I don't know how to turn off this option in VSCode) +* `CHG` [#1344] improve `---@see` +* `CHG` [#1484] setting `runtime.special` supports fields + ```jsonc + { + "runtime.special": { + "sandbox.require": "require" + } + } + ``` +* `CHG` [#1533] supports completion with table field of function +* `CHG` [#1457] infer parameter type by function type + ```lua + ---@type fun(x: number) + local function f(x) --> `x` is inferred as `number` + end + ``` +* `CHG` [#1663] check parameter types of generic extends + ```lua + ---@generic T: string | boolean + ---@param x T + ---@return T + local function f(x) + return x + end + + local x = f(1) --> Warning: Cannot assign `integer` to parameter ``. + ``` +* `CHG` [#1434] type check: check the fields in table: + ```lua + ---@type table + local x + + ---@type table + local y + + x = y --> Warning: Cannot assign `` to `` + ``` +* `CHG` [#1374] type check: supports array part in literal table + ```lua + ---@type boolean[] + local t = { 1, 2, 3 } --> Warning: Cannot assign `integer` to `boolean` + ``` +* `CHG` `---@enum` supports runtime values +* `FIX` [#1479] +* `FIX` [#1480] +* `FIX` [#1567] +* `FIX` [#1593] +* `FIX` [#1595] +* `FIX` [#1599] +* `FIX` [#1606] +* `FIX` [#1608] +* `FIX` [#1637] +* `FIX` [#1640] +* `FIX` [#1642] +* `FIX` [#1662] +* `FIX` [#1672] + +[#1153]: https://github.com/LuaLS/lua-language-server/issues/1153 +[#1177]: https://github.com/LuaLS/lua-language-server/issues/1177 +[#1201]: https://github.com/LuaLS/lua-language-server/issues/1201 +[#1202]: https://github.com/LuaLS/lua-language-server/issues/1202 +[#1332]: https://github.com/LuaLS/lua-language-server/issues/1332 +[#1344]: https://github.com/LuaLS/lua-language-server/issues/1344 +[#1374]: https://github.com/LuaLS/lua-language-server/issues/1374 +[#1434]: https://github.com/LuaLS/lua-language-server/issues/1434 +[#1457]: https://github.com/LuaLS/lua-language-server/issues/1457 +[#1458]: https://github.com/LuaLS/lua-language-server/issues/1458 +[#1479]: https://github.com/LuaLS/lua-language-server/issues/1479 +[#1480]: https://github.com/LuaLS/lua-language-server/issues/1480 +[#1484]: https://github.com/LuaLS/lua-language-server/issues/1484 +[#1533]: https://github.com/LuaLS/lua-language-server/issues/1533 +[#1557]: https://github.com/LuaLS/lua-language-server/issues/1557 +[#1558]: https://github.com/LuaLS/lua-language-server/issues/1558 +[#1561]: https://github.com/LuaLS/lua-language-server/issues/1561 +[#1567]: https://github.com/LuaLS/lua-language-server/issues/1567 +[#1575]: https://github.com/LuaLS/lua-language-server/issues/1575 +[#1582]: https://github.com/LuaLS/lua-language-server/issues/1582 +[#1593]: https://github.com/LuaLS/lua-language-server/issues/1593 +[#1595]: https://github.com/LuaLS/lua-language-server/issues/1595 +[#1599]: https://github.com/LuaLS/lua-language-server/issues/1599 +[#1606]: https://github.com/LuaLS/lua-language-server/issues/1606 +[#1608]: https://github.com/LuaLS/lua-language-server/issues/1608 +[#1626]: https://github.com/LuaLS/lua-language-server/issues/1626 +[#1637]: https://github.com/LuaLS/lua-language-server/issues/1637 +[#1640]: https://github.com/LuaLS/lua-language-server/issues/1640 +[#1641]: https://github.com/LuaLS/lua-language-server/issues/1641 +[#1642]: https://github.com/LuaLS/lua-language-server/issues/1642 +[#1662]: https://github.com/LuaLS/lua-language-server/issues/1662 +[#1663]: https://github.com/LuaLS/lua-language-server/issues/1663 +[#1670]: https://github.com/LuaLS/lua-language-server/issues/1670 +[#1672]: https://github.com/LuaLS/lua-language-server/issues/1672 + +## 3.5.6 +`2022-9-16` +* `FIX` [#1439](https://github.com/LuaLS/lua-language-server/issues/1439) +* `FIX` [#1467](https://github.com/LuaLS/lua-language-server/issues/1467) +* `FIX` [#1506](https://github.com/LuaLS/lua-language-server/issues/1506) +* `FIX` [#1537](https://github.com/LuaLS/lua-language-server/issues/1537) + +## 3.5.5 +`2022-9-7` +* `FIX` [#1529](https://github.com/LuaLS/lua-language-server/issues/1529) +* `FIX` [#1530](https://github.com/LuaLS/lua-language-server/issues/1530) + +## 3.5.4 +`2022-9-6` +* `NEW` `type-formatting`: fix wrong indentation of VSCode +* `CHG` `document-symbol`: redesigned to better support for `Sticky Scroll` feature of VSCode +* `FIX` `diagnostics.workspaceDelay` can not prevent first workspace diagnostic +* `FIX` [#1476](https://github.com/LuaLS/lua-language-server/issues/1476) +* `FIX` [#1490](https://github.com/LuaLS/lua-language-server/issues/1490) +* `FIX` [#1493](https://github.com/LuaLS/lua-language-server/issues/1493) +* `FIX` [#1499](https://github.com/LuaLS/lua-language-server/issues/1499) +* `FIX` [#1526](https://github.com/LuaLS/lua-language-server/issues/1526) + +## 3.5.3 +`2022-8-13` +* `FIX` [#1409](https://github.com/LuaLS/lua-language-server/issues/1409) +* `FIX` [#1422](https://github.com/LuaLS/lua-language-server/issues/1422) +* `FIX` [#1425](https://github.com/LuaLS/lua-language-server/issues/1425) +* `FIX` [#1428](https://github.com/LuaLS/lua-language-server/issues/1428) +* `FIX` [#1430](https://github.com/LuaLS/lua-language-server/issues/1430) +* `FIX` [#1431](https://github.com/LuaLS/lua-language-server/issues/1431) +* `FIX` [#1446](https://github.com/LuaLS/lua-language-server/issues/1446) +* `FIX` [#1451](https://github.com/LuaLS/lua-language-server/issues/1451) +* `FIX` [#1461](https://github.com/LuaLS/lua-language-server/issues/1461) +* `FIX` [#1463](https://github.com/LuaLS/lua-language-server/issues/1463) + ## 3.5.2 `2022-8-1` -* `FIX` [#1395](https://github.com/sumneko/lua-language-server/issues/1395) -* `FIX` [#1403](https://github.com/sumneko/lua-language-server/issues/1403) -* `FIX` [#1405](https://github.com/sumneko/lua-language-server/issues/1405) -* `FIX` [#1406](https://github.com/sumneko/lua-language-server/issues/1406) -* `FIX` [#1418](https://github.com/sumneko/lua-language-server/issues/1418) +* `FIX` [#1395](https://github.com/LuaLS/lua-language-server/issues/1395) +* `FIX` [#1403](https://github.com/LuaLS/lua-language-server/issues/1403) +* `FIX` [#1405](https://github.com/LuaLS/lua-language-server/issues/1405) +* `FIX` [#1406](https://github.com/LuaLS/lua-language-server/issues/1406) +* `FIX` [#1418](https://github.com/LuaLS/lua-language-server/issues/1418) ## 3.5.1 `2022-7-26` -* `NEW` supports [color](https://github.com/sumneko/lua-language-server/pull/1379) +* `NEW` supports [color](https://github.com/LuaLS/lua-language-server/pull/1379) * `NEW` setting `Lua.runtime.pluginArgs` * `CHG` setting `type.castNumberToInteger` default by `true` * `CHG` improve supports for multi-workspace -* `FIX` [#1354](https://github.com/sumneko/lua-language-server/issues/1354) -* `FIX` [#1355](https://github.com/sumneko/lua-language-server/issues/1355) -* `FIX` [#1363](https://github.com/sumneko/lua-language-server/issues/1363) -* `FIX` [#1365](https://github.com/sumneko/lua-language-server/issues/1365) -* `FIX` [#1367](https://github.com/sumneko/lua-language-server/issues/1367) -* `FIX` [#1368](https://github.com/sumneko/lua-language-server/issues/1368) -* `FIX` [#1370](https://github.com/sumneko/lua-language-server/issues/1370) -* `FIX` [#1375](https://github.com/sumneko/lua-language-server/issues/1375) -* `FIX` [#1391](https://github.com/sumneko/lua-language-server/issues/1391) +* `FIX` [#1354](https://github.com/LuaLS/lua-language-server/issues/1354) +* `FIX` [#1355](https://github.com/LuaLS/lua-language-server/issues/1355) +* `FIX` [#1363](https://github.com/LuaLS/lua-language-server/issues/1363) +* `FIX` [#1365](https://github.com/LuaLS/lua-language-server/issues/1365) +* `FIX` [#1367](https://github.com/LuaLS/lua-language-server/issues/1367) +* `FIX` [#1368](https://github.com/LuaLS/lua-language-server/issues/1368) +* `FIX` [#1370](https://github.com/LuaLS/lua-language-server/issues/1370) +* `FIX` [#1375](https://github.com/LuaLS/lua-language-server/issues/1375) +* `FIX` [#1391](https://github.com/LuaLS/lua-language-server/issues/1391) ## 3.5.0 `2022-7-19` @@ -85,18 +649,18 @@ local x ``` * `CHG` signature: only show signatures matching the entered parameters -* `FIX` [#880](https://github.com/sumneko/lua-language-server/issues/880) -* `FIX` [#1284](https://github.com/sumneko/lua-language-server/issues/1284) -* `FIX` [#1292](https://github.com/sumneko/lua-language-server/issues/1292) -* `FIX` [#1294](https://github.com/sumneko/lua-language-server/issues/1294) -* `FIX` [#1306](https://github.com/sumneko/lua-language-server/issues/1306) -* `FIX` [#1311](https://github.com/sumneko/lua-language-server/issues/1311) -* `FIX` [#1317](https://github.com/sumneko/lua-language-server/issues/1317) -* `FIX` [#1320](https://github.com/sumneko/lua-language-server/issues/1320) -* `FIX` [#1330](https://github.com/sumneko/lua-language-server/issues/1330) -* `FIX` [#1345](https://github.com/sumneko/lua-language-server/issues/1345) -* `FIX` [#1346](https://github.com/sumneko/lua-language-server/issues/1346) -* `FIX` [#1348](https://github.com/sumneko/lua-language-server/issues/1348) +* `FIX` [#880](https://github.com/LuaLS/lua-language-server/issues/880) +* `FIX` [#1284](https://github.com/LuaLS/lua-language-server/issues/1284) +* `FIX` [#1292](https://github.com/LuaLS/lua-language-server/issues/1292) +* `FIX` [#1294](https://github.com/LuaLS/lua-language-server/issues/1294) +* `FIX` [#1306](https://github.com/LuaLS/lua-language-server/issues/1306) +* `FIX` [#1311](https://github.com/LuaLS/lua-language-server/issues/1311) +* `FIX` [#1317](https://github.com/LuaLS/lua-language-server/issues/1317) +* `FIX` [#1320](https://github.com/LuaLS/lua-language-server/issues/1320) +* `FIX` [#1330](https://github.com/LuaLS/lua-language-server/issues/1330) +* `FIX` [#1345](https://github.com/LuaLS/lua-language-server/issues/1345) +* `FIX` [#1346](https://github.com/LuaLS/lua-language-server/issues/1346) +* `FIX` [#1348](https://github.com/LuaLS/lua-language-server/issues/1348) ## 3.4.2 `2022-7-6` @@ -105,8 +669,8 @@ * `CHG` completion: `completion.callSnippet` no longer generate parameter types * `CHG` hover: show `---@type {x: number, y: number}` as detail instead of `table` * `CHG` dose not infer as `nil` by `t.field = nil` -* `FIX` [#1278](https://github.com/sumneko/lua-language-server/issues/1278) -* `FIX` [#1288](https://github.com/sumneko/lua-language-server/issues/1288) +* `FIX` [#1278](https://github.com/LuaLS/lua-language-server/issues/1278) +* `FIX` [#1288](https://github.com/LuaLS/lua-language-server/issues/1288) ## 3.4.1 `2022-7-5` @@ -120,13 +684,13 @@ ---@class B: A local b = setmetatable({}, { __index = a }) -- OK! ``` -* `FIX` [#1256](https://github.com/sumneko/lua-language-server/issues/1256) -* `FIX` [#1257](https://github.com/sumneko/lua-language-server/issues/1257) -* `FIX` [#1267](https://github.com/sumneko/lua-language-server/issues/1267) -* `FIX` [#1269](https://github.com/sumneko/lua-language-server/issues/1269) -* `FIX` [#1273](https://github.com/sumneko/lua-language-server/issues/1273) -* `FIX` [#1275](https://github.com/sumneko/lua-language-server/issues/1275) -* `FIX` [#1279](https://github.com/sumneko/lua-language-server/issues/1279) +* `FIX` [#1256](https://github.com/LuaLS/lua-language-server/issues/1256) +* `FIX` [#1257](https://github.com/LuaLS/lua-language-server/issues/1257) +* `FIX` [#1267](https://github.com/LuaLS/lua-language-server/issues/1267) +* `FIX` [#1269](https://github.com/LuaLS/lua-language-server/issues/1269) +* `FIX` [#1273](https://github.com/LuaLS/lua-language-server/issues/1273) +* `FIX` [#1275](https://github.com/LuaLS/lua-language-server/issues/1275) +* `FIX` [#1279](https://github.com/LuaLS/lua-language-server/issues/1279) ## 3.4.0 `2022-6-29` @@ -174,20 +738,20 @@ * `CHG` improve experience for diagnostics and semantic-tokens * `FIX` diagnostics flash when opening a file * `FIX` sometimes workspace diagnostics are not triggered -* `FIX` [#1228](https://github.com/sumneko/lua-language-server/issues/1228) -* `FIX` [#1229](https://github.com/sumneko/lua-language-server/issues/1229) -* `FIX` [#1242](https://github.com/sumneko/lua-language-server/issues/1242) -* `FIX` [#1243](https://github.com/sumneko/lua-language-server/issues/1243) -* `FIX` [#1249](https://github.com/sumneko/lua-language-server/issues/1249) +* `FIX` [#1228](https://github.com/LuaLS/lua-language-server/issues/1228) +* `FIX` [#1229](https://github.com/LuaLS/lua-language-server/issues/1229) +* `FIX` [#1242](https://github.com/LuaLS/lua-language-server/issues/1242) +* `FIX` [#1243](https://github.com/LuaLS/lua-language-server/issues/1243) +* `FIX` [#1249](https://github.com/LuaLS/lua-language-server/issues/1249) ## 3.3.1 `2022-6-17` -* `FIX` [#1213](https://github.com/sumneko/lua-language-server/issues/1213) -* `FIX` [#1215](https://github.com/sumneko/lua-language-server/issues/1215) -* `FIX` [#1217](https://github.com/sumneko/lua-language-server/issues/1217) -* `FIX` [#1218](https://github.com/sumneko/lua-language-server/issues/1218) -* `FIX` [#1220](https://github.com/sumneko/lua-language-server/issues/1220) -* `FIX` [#1223](https://github.com/sumneko/lua-language-server/issues/1223) +* `FIX` [#1213](https://github.com/LuaLS/lua-language-server/issues/1213) +* `FIX` [#1215](https://github.com/LuaLS/lua-language-server/issues/1215) +* `FIX` [#1217](https://github.com/LuaLS/lua-language-server/issues/1217) +* `FIX` [#1218](https://github.com/LuaLS/lua-language-server/issues/1218) +* `FIX` [#1220](https://github.com/LuaLS/lua-language-server/issues/1220) +* `FIX` [#1223](https://github.com/LuaLS/lua-language-server/issues/1223) ## 3.3.0 `2022-6-15` @@ -232,15 +796,15 @@ ``` * `CHG` infer type by `>`/`<`/`>=`/`<=` * `FIX` with clients that support LSP 3.17 (VSCode), workspace diagnostics are triggered every time when opening a file. -* `FIX` [#1204](https://github.com/sumneko/lua-language-server/issues/1204) -* `FIX` [#1208](https://github.com/sumneko/lua-language-server/issues/1208) +* `FIX` [#1204](https://github.com/LuaLS/lua-language-server/issues/1204) +* `FIX` [#1208](https://github.com/LuaLS/lua-language-server/issues/1208) ## 3.2.5 `2022-6-9` * `NEW` provide config docs in `LUA_LANGUAGE_SERVER/doc/` -* `FIX` [#1148](https://github.com/sumneko/lua-language-server/issues/1148) -* `FIX` [#1149](https://github.com/sumneko/lua-language-server/issues/1149) -* `FIX` [#1192](https://github.com/sumneko/lua-language-server/issues/1192) +* `FIX` [#1148](https://github.com/LuaLS/lua-language-server/issues/1148) +* `FIX` [#1149](https://github.com/LuaLS/lua-language-server/issues/1149) +* `FIX` [#1192](https://github.com/LuaLS/lua-language-server/issues/1192) ## 3.2.4 `2022-5-25` @@ -252,13 +816,13 @@ * `CHG` show warning message when scanning more than 100,000 files. * `CHG` upgrade [LSP](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) to `3.17` * `FIX` hover: can not union `table` with other basic types -* `FIX` [#1125](https://github.com/sumneko/lua-language-server/issues/1125) -* `FIX` [#1131](https://github.com/sumneko/lua-language-server/issues/1131) -* `FIX` [#1134](https://github.com/sumneko/lua-language-server/issues/1134) -* `FIX` [#1141](https://github.com/sumneko/lua-language-server/issues/1141) -* `FIX` [#1144](https://github.com/sumneko/lua-language-server/issues/1144) -* `FIX` [#1150](https://github.com/sumneko/lua-language-server/issues/1150) -* `FIX` [#1155](https://github.com/sumneko/lua-language-server/issues/1155) +* `FIX` [#1125](https://github.com/LuaLS/lua-language-server/issues/1125) +* `FIX` [#1131](https://github.com/LuaLS/lua-language-server/issues/1131) +* `FIX` [#1134](https://github.com/LuaLS/lua-language-server/issues/1134) +* `FIX` [#1141](https://github.com/LuaLS/lua-language-server/issues/1141) +* `FIX` [#1144](https://github.com/LuaLS/lua-language-server/issues/1144) +* `FIX` [#1150](https://github.com/LuaLS/lua-language-server/issues/1150) +* `FIX` [#1155](https://github.com/LuaLS/lua-language-server/issues/1155) ## 3.2.3 `2022-5-16` @@ -266,15 +830,15 @@ * `CHG` dose not load files in symbol links * `FIX` memory leak with symbol links * `FIX` diagnostic: send empty results to every file after startup -* `FIX` [#1103](https://github.com/sumneko/lua-language-server/issues/1103) -* `FIX` [#1107](https://github.com/sumneko/lua-language-server/issues/1107) +* `FIX` [#1103](https://github.com/LuaLS/lua-language-server/issues/1103) +* `FIX` [#1107](https://github.com/LuaLS/lua-language-server/issues/1107) ## 3.2.2 `2022-4-26` * `FIX` diagnostic: `unused-function` cannot handle recursion correctly -* `FIX` [#1092](https://github.com/sumneko/lua-language-server/issues/1092) -* `FIX` [#1093](https://github.com/sumneko/lua-language-server/issues/1093) -* `FIX` runtime errors reported by telemetry, see [#1091](https://github.com/sumneko/lua-language-server/issues/1091) +* `FIX` [#1092](https://github.com/LuaLS/lua-language-server/issues/1092) +* `FIX` [#1093](https://github.com/LuaLS/lua-language-server/issues/1093) +* `FIX` runtime errors reported by telemetry, see [#1091](https://github.com/LuaLS/lua-language-server/issues/1091) ## 3.2.1 `2022-4-25` @@ -316,10 +880,10 @@ * `NEW` diagnostic: `need-check-nil` * `CHG` diagnostic: no longer mark `redundant-parameter` as `Unnecessary` * `FIX` diagnostic: `unused-function` does not recognize recursion -* `FIX` [#1051](https://github.com/sumneko/lua-language-server/issues/1051) -* `FIX` [#1072](https://github.com/sumneko/lua-language-server/issues/1072) -* `FIX` [#1077](https://github.com/sumneko/lua-language-server/issues/1077) -* `FIX` [#1088](https://github.com/sumneko/lua-language-server/issues/1088) +* `FIX` [#1051](https://github.com/LuaLS/lua-language-server/issues/1051) +* `FIX` [#1072](https://github.com/LuaLS/lua-language-server/issues/1072) +* `FIX` [#1077](https://github.com/LuaLS/lua-language-server/issues/1077) +* `FIX` [#1088](https://github.com/LuaLS/lua-language-server/issues/1088) * `FIX` runtime errors ## 3.1.0 @@ -329,90 +893,90 @@ * `CHG` hover: split `local` into `local` / `parameter` / `upvalue` / `self`. * `CHG` hover: added parentheses to some words, such as `global` / `field` / `class`. * `FIX` definition of `table` -* `FIX` [#994](https://github.com/sumneko/lua-language-server/issues/994) -* `FIX` [#1057](https://github.com/sumneko/lua-language-server/issues/1057) -* `FIX` runtime errors reported by telemetry, see [#1058](https://github.com/sumneko/lua-language-server/issues/1058) +* `FIX` [#994](https://github.com/LuaLS/lua-language-server/issues/994) +* `FIX` [#1057](https://github.com/LuaLS/lua-language-server/issues/1057) +* `FIX` runtime errors reported by telemetry, see [#1058](https://github.com/LuaLS/lua-language-server/issues/1058) ## 3.0.2 `2022-4-15` * `FIX` `table[string] -> boolean` * `FIX` goto `type definition` -* `FIX` [#1050](https://github.com/sumneko/lua-language-server/issues/1050) +* `FIX` [#1050](https://github.com/LuaLS/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) +* `FIX` [#1033](https://github.com/LuaLS/lua-language-server/issues/1033) +* `FIX` [#1034](https://github.com/LuaLS/lua-language-server/issues/1034) +* `FIX` [#1035](https://github.com/LuaLS/lua-language-server/issues/1035) +* `FIX` [#1036](https://github.com/LuaLS/lua-language-server/issues/1036) +* `FIX` runtime errors reported by telemetry, see [#1037](https://github.com/LuaLS/lua-language-server/issues/1037) ## 3.0.0 `2022-4-10` -* `CHG` [break changes](https://github.com/sumneko/lua-language-server/issues/980) +* `CHG` [break changes](https://github.com/LuaLS/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) +* `FIX` [#1027](https://github.com/LuaLS/lua-language-server/issues/1027) +* `FIX` [#1028](https://github.com/LuaLS/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` [#976](https://github.com/LuaLS/lua-language-server/issues/976) +* `FIX` [#995](https://github.com/LuaLS/lua-language-server/issues/995) +* `FIX` [#1004](https://github.com/LuaLS/lua-language-server/issues/1004) +* `FIX` [#1008](https://github.com/LuaLS/lua-language-server/issues/1008) +* `FIX` [#1009](https://github.com/LuaLS/lua-language-server/issues/1009) +* `FIX` [#1011](https://github.com/LuaLS/lua-language-server/issues/1011) +* `FIX` [#1014](https://github.com/LuaLS/lua-language-server/issues/1014) +* `FIX` [#1016](https://github.com/LuaLS/lua-language-server/issues/1016) +* `FIX` [#1017](https://github.com/LuaLS/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). +* `NEW` diagnosis report, [read more](https://luals.github.io/wiki/diagnosis-report/) +* `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/LuaLS/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) +* `FIX` [#965](https://github.com/LuaLS/lua-language-server/issues/965) +* `FIX` [#975](https://github.com/LuaLS/lua-language-server/issues/975) ## 2.6.6 `2022-2-21` -* `NEW` formatter preview, use `--preview` to enable this feature, [read more](https://github.com/sumneko/lua-language-server/issues/960) -* `FIX` [#958](https://github.com/sumneko/lua-language-server/issues/958) +* `NEW` formatter preview, use `--preview` to enable this feature, [read more](https://github.com/LuaLS/lua-language-server/issues/960) +* `FIX` [#958](https://github.com/LuaLS/lua-language-server/issues/958) * `FIX` runtime errors ## 2.6.5 `2022-2-17` * `FIX` telemetry is not disabled by default (since 2.6.0) -* `FIX` [#934](https://github.com/sumneko/lua-language-server/issues/934) -* `FIX` [#952](https://github.com/sumneko/lua-language-server/issues/952) +* `FIX` [#934](https://github.com/LuaLS/lua-language-server/issues/934) +* `FIX` [#952](https://github.com/LuaLS/lua-language-server/issues/952) ## 2.6.4 `2022-2-9` * `CHG` completion: reduced sorting priority for postfix completion -* `FIX` [#936](https://github.com/sumneko/lua-language-server/issues/936) -* `FIX` [#937](https://github.com/sumneko/lua-language-server/issues/937) -* `FIX` [#940](https://github.com/sumneko/lua-language-server/issues/940) -* `FIX` [#941](https://github.com/sumneko/lua-language-server/issues/941) -* `FIX` [#941](https://github.com/sumneko/lua-language-server/issues/942) -* `FIX` [#943](https://github.com/sumneko/lua-language-server/issues/943) -* `FIX` [#946](https://github.com/sumneko/lua-language-server/issues/946) +* `FIX` [#936](https://github.com/LuaLS/lua-language-server/issues/936) +* `FIX` [#937](https://github.com/LuaLS/lua-language-server/issues/937) +* `FIX` [#940](https://github.com/LuaLS/lua-language-server/issues/940) +* `FIX` [#941](https://github.com/LuaLS/lua-language-server/issues/941) +* `FIX` [#941](https://github.com/LuaLS/lua-language-server/issues/942) +* `FIX` [#943](https://github.com/LuaLS/lua-language-server/issues/943) +* `FIX` [#946](https://github.com/LuaLS/lua-language-server/issues/946) ## 2.6.3 `2022-1-25` * `FIX` new files are not loaded correctly -* `FIX` [#923](https://github.com/sumneko/lua-language-server/issues/923) -* `FIX` [#926](https://github.com/sumneko/lua-language-server/issues/926) +* `FIX` [#923](https://github.com/LuaLS/lua-language-server/issues/923) +* `FIX` [#926](https://github.com/LuaLS/lua-language-server/issues/926) ## 2.6.2 `2022-1-25` -* `FIX` [#925](https://github.com/sumneko/lua-language-server/issues/925) +* `FIX` [#925](https://github.com/LuaLS/lua-language-server/issues/925) ## 2.6.1 `2022-1-24` @@ -423,13 +987,13 @@ * `CHG` improve performance * `FIX` modify luarc failed * `FIX` library files not recognized correctly -* `FIX` [#903](https://github.com/sumneko/lua-language-server/issues/903) -* `FIX` [#906](https://github.com/sumneko/lua-language-server/issues/906) -* `FIX` [#920](https://github.com/sumneko/lua-language-server/issues/920) +* `FIX` [#903](https://github.com/LuaLS/lua-language-server/issues/903) +* `FIX` [#906](https://github.com/LuaLS/lua-language-server/issues/906) +* `FIX` [#920](https://github.com/LuaLS/lua-language-server/issues/920) ## 2.6.0 `2022-1-13` -* `NEW` supports multi-workspace in server side, for developers of language clients, please [read here](https://github.com/sumneko/lua-language-server/wiki/Multi-workspace-supports) to learn more. +* `NEW` supports multi-workspace in server side, for developers of language clients, please [read here](https://luals.github.io/wiki/developing/#multiple-workspace-support) to learn more. * `NEW` setting: + `Lua.hint.arrayIndex` + `Lua.semantic.enable` @@ -440,17 +1004,17 @@ * `CHG` completion: can be triggered in `LuaDoc` and strings * `CHG` diagnostic: smoother * `CHG` settings `Lua.color.mode` removed -* `FIX` [#876](https://github.com/sumneko/lua-language-server/issues/876) -* `FIX` [#879](https://github.com/sumneko/lua-language-server/issues/879) -* `FIX` [#884](https://github.com/sumneko/lua-language-server/issues/884) -* `FIX` [#885](https://github.com/sumneko/lua-language-server/issues/885) -* `FIX` [#886](https://github.com/sumneko/lua-language-server/issues/886) -* `FIX` [#902](https://github.com/sumneko/lua-language-server/issues/902) +* `FIX` [#876](https://github.com/LuaLS/lua-language-server/issues/876) +* `FIX` [#879](https://github.com/LuaLS/lua-language-server/issues/879) +* `FIX` [#884](https://github.com/LuaLS/lua-language-server/issues/884) +* `FIX` [#885](https://github.com/LuaLS/lua-language-server/issues/885) +* `FIX` [#886](https://github.com/LuaLS/lua-language-server/issues/886) +* `FIX` [#902](https://github.com/LuaLS/lua-language-server/issues/902) ## 2.5.6 `2021-12-27` * `CHG` diagnostic: now syntax errors in `LuaDoc` are shown as `Warning` -* `FIX` [#863](https://github.com/sumneko/lua-language-server/issues/863) +* `FIX` [#863](https://github.com/LuaLS/lua-language-server/issues/863) * `FIX` return type of `math.floor` * `FIX` runtime errors @@ -460,25 +1024,25 @@ ## 2.5.4 `2021-12-16` -* `FIX` [#847](https://github.com/sumneko/lua-language-server/issues/847) -* `FIX` [#848](https://github.com/sumneko/lua-language-server/issues/848) +* `FIX` [#847](https://github.com/LuaLS/lua-language-server/issues/847) +* `FIX` [#848](https://github.com/LuaLS/lua-language-server/issues/848) * `FIX` completion: incorrect cache * `FIX` hover: always view string ## 2.5.3 `2021-12-6` -* `FIX` [#842](https://github.com/sumneko/lua-language-server/issues/844) -* `FIX` [#844](https://github.com/sumneko/lua-language-server/issues/844) +* `FIX` [#842](https://github.com/LuaLS/lua-language-server/issues/844) +* `FIX` [#844](https://github.com/LuaLS/lua-language-server/issues/844) ## 2.5.2 `2021-12-2` -* `FIX` [#815](https://github.com/sumneko/lua-language-server/issues/815) -* `FIX` [#825](https://github.com/sumneko/lua-language-server/issues/825) -* `FIX` [#826](https://github.com/sumneko/lua-language-server/issues/826) -* `FIX` [#827](https://github.com/sumneko/lua-language-server/issues/827) -* `FIX` [#831](https://github.com/sumneko/lua-language-server/issues/831) -* `FIX` [#837](https://github.com/sumneko/lua-language-server/issues/837) -* `FIX` [#838](https://github.com/sumneko/lua-language-server/issues/838) +* `FIX` [#815](https://github.com/LuaLS/lua-language-server/issues/815) +* `FIX` [#825](https://github.com/LuaLS/lua-language-server/issues/825) +* `FIX` [#826](https://github.com/LuaLS/lua-language-server/issues/826) +* `FIX` [#827](https://github.com/LuaLS/lua-language-server/issues/827) +* `FIX` [#831](https://github.com/LuaLS/lua-language-server/issues/831) +* `FIX` [#837](https://github.com/LuaLS/lua-language-server/issues/837) +* `FIX` [#838](https://github.com/LuaLS/lua-language-server/issues/838) * `FIX` postfix * `FIX` runtime errors @@ -494,7 +1058,7 @@ + `Lua.completion.postfix`: the symbol that triggers postfix, default is `@` * `NEW` add supports for `lovr` * `NEW` file encoding supports `utf16le` and `utf16be` -* `NEW` full IntelliSense supports for literal tables, see [#720](https://github.com/sumneko/lua-language-server/issues/720) and [#727](https://github.com/sumneko/lua-language-server/issues/727) +* `NEW` full IntelliSense supports for literal tables, see [#720](https://github.com/LuaLS/lua-language-server/issues/720) and [#727](https://github.com/LuaLS/lua-language-server/issues/727) * `NEW` `LuaDoc` annotations: + `---@async`: mark a function as async + `---@nodiscard`: the return value of the marking function cannot be discarded @@ -519,20 +1083,20 @@ ## 2.4.11 `2021-11-25` -* `FIX` [#816](https://github.com/sumneko/lua-language-server/issues/816) -* `FIX` [#817](https://github.com/sumneko/lua-language-server/issues/817) -* `FIX` [#818](https://github.com/sumneko/lua-language-server/issues/818) -* `FIX` [#820](https://github.com/sumneko/lua-language-server/issues/820) +* `FIX` [#816](https://github.com/LuaLS/lua-language-server/issues/816) +* `FIX` [#817](https://github.com/LuaLS/lua-language-server/issues/817) +* `FIX` [#818](https://github.com/LuaLS/lua-language-server/issues/818) +* `FIX` [#820](https://github.com/LuaLS/lua-language-server/issues/820) ## 2.4.10 `2021-11-23` -* `FIX` [#790](https://github.com/sumneko/lua-language-server/issues/790) -* `FIX` [#798](https://github.com/sumneko/lua-language-server/issues/798) -* `FIX` [#804](https://github.com/sumneko/lua-language-server/issues/804) -* `FIX` [#805](https://github.com/sumneko/lua-language-server/issues/805) -* `FIX` [#806](https://github.com/sumneko/lua-language-server/issues/806) -* `FIX` [#807](https://github.com/sumneko/lua-language-server/issues/807) -* `FIX` [#809](https://github.com/sumneko/lua-language-server/issues/809) +* `FIX` [#790](https://github.com/LuaLS/lua-language-server/issues/790) +* `FIX` [#798](https://github.com/LuaLS/lua-language-server/issues/798) +* `FIX` [#804](https://github.com/LuaLS/lua-language-server/issues/804) +* `FIX` [#805](https://github.com/LuaLS/lua-language-server/issues/805) +* `FIX` [#806](https://github.com/LuaLS/lua-language-server/issues/806) +* `FIX` [#807](https://github.com/LuaLS/lua-language-server/issues/807) +* `FIX` [#809](https://github.com/LuaLS/lua-language-server/issues/809) ## 2.4.9 `2021-11-18` @@ -542,31 +1106,31 @@ + `Lua.IntelliSense.traceBeSetted` + `Lua.IntelliSense.traceFieldInject` - [read more](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) + [read more](https://github.com/LuaLS/lua-language-server/wiki/IntelliSense-optional-features) ## 2.4.8 `2021-11-15` * `FIX` incorrect IntelliSense in specific situations -* `FIX` [#777](https://github.com/sumneko/lua-language-server/issues/777) -* `FIX` [#778](https://github.com/sumneko/lua-language-server/issues/778) -* `FIX` [#779](https://github.com/sumneko/lua-language-server/issues/779) -* `FIX` [#780](https://github.com/sumneko/lua-language-server/issues/780) +* `FIX` [#777](https://github.com/LuaLS/lua-language-server/issues/777) +* `FIX` [#778](https://github.com/LuaLS/lua-language-server/issues/778) +* `FIX` [#779](https://github.com/LuaLS/lua-language-server/issues/779) +* `FIX` [#780](https://github.com/LuaLS/lua-language-server/issues/780) ## 2.4.7 `2021-10-27` -* `FIX` [#762](https://github.com/sumneko/lua-language-server/issues/762) +* `FIX` [#762](https://github.com/LuaLS/lua-language-server/issues/762) ## 2.4.6 `2021-10-26` * `NEW` diagnostic: `redundant-return` -* `FIX` [#744](https://github.com/sumneko/lua-language-server/issues/744) -* `FIX` [#748](https://github.com/sumneko/lua-language-server/issues/748) -* `FIX` [#749](https://github.com/sumneko/lua-language-server/issues/749) -* `FIX` [#752](https://github.com/sumneko/lua-language-server/issues/752) -* `FIX` [#753](https://github.com/sumneko/lua-language-server/issues/753) -* `FIX` [#756](https://github.com/sumneko/lua-language-server/issues/756) -* `FIX` [#758](https://github.com/sumneko/lua-language-server/issues/758) -* `FIX` [#760](https://github.com/sumneko/lua-language-server/issues/760) +* `FIX` [#744](https://github.com/LuaLS/lua-language-server/issues/744) +* `FIX` [#748](https://github.com/LuaLS/lua-language-server/issues/748) +* `FIX` [#749](https://github.com/LuaLS/lua-language-server/issues/749) +* `FIX` [#752](https://github.com/LuaLS/lua-language-server/issues/752) +* `FIX` [#753](https://github.com/LuaLS/lua-language-server/issues/753) +* `FIX` [#756](https://github.com/LuaLS/lua-language-server/issues/756) +* `FIX` [#758](https://github.com/LuaLS/lua-language-server/issues/758) +* `FIX` [#760](https://github.com/LuaLS/lua-language-server/issues/760) ## 2.4.5 `2021-10-18` @@ -575,31 +1139,31 @@ ## 2.4.4 `2021-10-15` * `CHG` improve `.luarc.json` -* `FIX` [#722](https://github.com/sumneko/lua-language-server/issues/722) +* `FIX` [#722](https://github.com/LuaLS/lua-language-server/issues/722) ## 2.4.3 `2021-10-13` -* `FIX` [#713](https://github.com/sumneko/lua-language-server/issues/713) -* `FIX` [#718](https://github.com/sumneko/lua-language-server/issues/718) -* `FIX` [#719](https://github.com/sumneko/lua-language-server/issues/719) -* `FIX` [#725](https://github.com/sumneko/lua-language-server/issues/725) -* `FIX` [#729](https://github.com/sumneko/lua-language-server/issues/729) -* `FIX` [#730](https://github.com/sumneko/lua-language-server/issues/730) +* `FIX` [#713](https://github.com/LuaLS/lua-language-server/issues/713) +* `FIX` [#718](https://github.com/LuaLS/lua-language-server/issues/718) +* `FIX` [#719](https://github.com/LuaLS/lua-language-server/issues/719) +* `FIX` [#725](https://github.com/LuaLS/lua-language-server/issues/725) +* `FIX` [#729](https://github.com/LuaLS/lua-language-server/issues/729) +* `FIX` [#730](https://github.com/LuaLS/lua-language-server/issues/730) * `FIX` runtime errors ## 2.4.2 `2021-10-8` -* `FIX` [#702](https://github.com/sumneko/lua-language-server/issues/702) -* `FIX` [#706](https://github.com/sumneko/lua-language-server/issues/706) -* `FIX` [#707](https://github.com/sumneko/lua-language-server/issues/707) -* `FIX` [#709](https://github.com/sumneko/lua-language-server/issues/709) -* `FIX` [#712](https://github.com/sumneko/lua-language-server/issues/712) +* `FIX` [#702](https://github.com/LuaLS/lua-language-server/issues/702) +* `FIX` [#706](https://github.com/LuaLS/lua-language-server/issues/706) +* `FIX` [#707](https://github.com/LuaLS/lua-language-server/issues/707) +* `FIX` [#709](https://github.com/LuaLS/lua-language-server/issues/709) +* `FIX` [#712](https://github.com/LuaLS/lua-language-server/issues/712) ## 2.4.1 `2021-10-2` * `FIX` broken with single file -* `FIX` [#698](https://github.com/sumneko/lua-language-server/issues/698) -* `FIX` [#699](https://github.com/sumneko/lua-language-server/issues/699) +* `FIX` [#698](https://github.com/LuaLS/lua-language-server/issues/698) +* `FIX` [#699](https://github.com/LuaLS/lua-language-server/issues/699) ## 2.4.0 `2021-10-1` @@ -652,18 +1216,18 @@ + `redundant-parameter` default severity to `Warning` + `redundant-value` default severity to `Warning` * `CHG` infer: more strict of calculation results -* `CHG` [#663](https://github.com/sumneko/lua-language-server/issues/663) +* `CHG` [#663](https://github.com/LuaLS/lua-language-server/issues/663) * `FIX` runtime errors * `FIX` hint: may show param-2 as `self` * `FIX` semantic: may fail when scrolling -* `FIX` [#647](https://github.com/sumneko/lua-language-server/issues/647) -* `FIX` [#660](https://github.com/sumneko/lua-language-server/issues/660) -* `FIX` [#673](https://github.com/sumneko/lua-language-server/issues/673) +* `FIX` [#647](https://github.com/LuaLS/lua-language-server/issues/647) +* `FIX` [#660](https://github.com/LuaLS/lua-language-server/issues/660) +* `FIX` [#673](https://github.com/LuaLS/lua-language-server/issues/673) ## 2.3.7 `2021-8-17` * `CHG` improve performance -* `FIX` [#244](https://github.com/sumneko/lua-language-server/issues/244) +* `FIX` [#244](https://github.com/LuaLS/lua-language-server/issues/244) ## 2.3.6 `2021-8-9` @@ -679,15 +1243,15 @@ ## 2.3.4 `2021-8-6` * `CHG` improve performance -* `FIX` [#625](https://github.com/sumneko/lua-language-server/issues/625) +* `FIX` [#625](https://github.com/LuaLS/lua-language-server/issues/625) ## 2.3.3 `2021-7-26` * `NEW` config supports prop -* `FIX` [#612](https://github.com/sumneko/lua-language-server/issues/612) -* `FIX` [#613](https://github.com/sumneko/lua-language-server/issues/613) -* `FIX` [#618](https://github.com/sumneko/lua-language-server/issues/618) -* `FIX` [#620](https://github.com/sumneko/lua-language-server/issues/620) +* `FIX` [#612](https://github.com/LuaLS/lua-language-server/issues/612) +* `FIX` [#613](https://github.com/LuaLS/lua-language-server/issues/613) +* `FIX` [#618](https://github.com/LuaLS/lua-language-server/issues/618) +* `FIX` [#620](https://github.com/LuaLS/lua-language-server/issues/620) ## 2.3.2 `2021-7-21` @@ -704,12 +1268,12 @@ ## 2.3.1 `2021-7-19` -* `NEW` setting `Lua.workspace.userThirdParty`, add private user [third-parth](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd) by this setting +* `NEW` setting `Lua.workspace.userThirdParty`, add private user [third-parth](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd) by this setting * `CHG` path in config supports `~/xxxx` * `FIX` `autoRequire` inserted incorrect code * `FIX` `autoRequire` may provide dumplicated options -* `FIX` [#606](https://github.com/sumneko/lua-language-server/issues/606) -* `FIX` [#607](https://github.com/sumneko/lua-language-server/issues/607) +* `FIX` [#606](https://github.com/LuaLS/lua-language-server/issues/606) +* `FIX` [#607](https://github.com/LuaLS/lua-language-server/issues/607) ## 2.3.0 `2021-7-16` @@ -744,10 +1308,10 @@ * `FIX` completion: displaying `@fenv` in `Lua 5.1` * `FIX` completion: incorrect at end of line * `FIX` when a file is renamed, the file will still be loaded even if the new file name has been set to ignore -* `FIX` [#596](https://github.com/sumneko/lua-language-server/issues/596) -* `FIX` [#597](https://github.com/sumneko/lua-language-server/issues/597) -* `FIX` [#598](https://github.com/sumneko/lua-language-server/issues/598) -* `FIX` [#601](https://github.com/sumneko/lua-language-server/issues/601) +* `FIX` [#596](https://github.com/LuaLS/lua-language-server/issues/596) +* `FIX` [#597](https://github.com/LuaLS/lua-language-server/issues/597) +* `FIX` [#598](https://github.com/LuaLS/lua-language-server/issues/598) +* `FIX` [#601](https://github.com/LuaLS/lua-language-server/issues/601) ## 2.2.3 `2021-7-9` @@ -755,7 +1319,7 @@ * `CHG` will not sleep anymore * `FIX` incorrect doc: `debug.getlocal` * `FIX` completion: incorrect callback -* `FIX` [#592](https://github.com/sumneko/lua-language-server/issues/592) +* `FIX` [#592](https://github.com/LuaLS/lua-language-server/issues/592) ## 2.2.2 `2021-7-9` @@ -796,7 +1360,7 @@ ## 2.1.0 `2021-7-2` -* `NEW` supports local config file, using `--configpath="config.json"`, [learn more here](https://github.com/sumneko/lua-language-server/wiki/Setting-without-VSCode) +* `NEW` supports local config file, using `--configpath="config.json"`, [learn more here](https://luals.github.io/wiki/usage/#--configpath) * `NEW` goto `type definition` * `NEW` infer type by callback param: ```lua @@ -809,7 +1373,7 @@ end) ``` * `NEW` optional field `---@field name? type` -* `CHG` [#549](https://github.com/sumneko/lua-language-server/issues/549) +* `CHG` [#549](https://github.com/LuaLS/lua-language-server/issues/549) * `CHG` diagnostics: always ignore the ignored files even if they are opened * `FIX` completion: `type() ==` may does not work @@ -830,20 +1394,20 @@ end ``` * `CHG` improve performance -* `FIX` [#580](https://github.com/sumneko/lua-language-server/issues/580) +* `FIX` [#580](https://github.com/LuaLS/lua-language-server/issues/580) ## 2.0.4 `2021-6-25` -* `FIX` [#550](https://github.com/sumneko/lua-language-server/issues/550) -* `FIX` [#555](https://github.com/sumneko/lua-language-server/issues/555) -* `FIX` [#574](https://github.com/sumneko/lua-language-server/issues/574) +* `FIX` [#550](https://github.com/LuaLS/lua-language-server/issues/550) +* `FIX` [#555](https://github.com/LuaLS/lua-language-server/issues/555) +* `FIX` [#574](https://github.com/LuaLS/lua-language-server/issues/574) ## 2.0.3 `2021-6-24` * `CHG` improve memory usage * `FIX` some dialog boxes block the initialization process * `FIX` diagnostics `undefined-field`: blocks main thread -* `FIX` [#565](https://github.com/sumneko/lua-language-server/issues/565) +* `FIX` [#565](https://github.com/LuaLS/lua-language-server/issues/565) ## 2.0.2 `2021-6-23` @@ -862,15 +1426,15 @@ ---after local f: fun(x: number): boolean ``` -* `FIX` [#558](https://github.com/sumneko/lua-language-server/issues/558) -* `FIX` [#567](https://github.com/sumneko/lua-language-server/issues/567) -* `FIX` [#568](https://github.com/sumneko/lua-language-server/issues/568) -* `FIX` [#570](https://github.com/sumneko/lua-language-server/issues/570) -* `FIX` [#571](https://github.com/sumneko/lua-language-server/issues/571) +* `FIX` [#558](https://github.com/LuaLS/lua-language-server/issues/558) +* `FIX` [#567](https://github.com/LuaLS/lua-language-server/issues/567) +* `FIX` [#568](https://github.com/LuaLS/lua-language-server/issues/568) +* `FIX` [#570](https://github.com/LuaLS/lua-language-server/issues/570) +* `FIX` [#571](https://github.com/LuaLS/lua-language-server/issues/571) ## 2.0.1 `2021-6-21` -* `FIX` [#566](https://github.com/sumneko/lua-language-server/issues/566) +* `FIX` [#566](https://github.com/LuaLS/lua-language-server/issues/566) ## 2.0.0 `2021-6-21` @@ -880,14 +1444,14 @@ * `CHG` setting `Lua.intelliSense.searchDepth`: removed * `CHG` setting `Lua.misc.parameters`: `string array` instead of `string` * `CHG` setting `Lua.develop.enable`, `Lua.develop.debuggerPort`, `Lua.develop.debuggerWait`: removed, use `Lua.misc.parameters` instead -* `FIX` [#441](https://github.com/sumneko/lua-language-server/issues/441) -* `FIX` [#493](https://github.com/sumneko/lua-language-server/issues/493) -* `FIX` [#531](https://github.com/sumneko/lua-language-server/issues/531) -* `FIX` [#542](https://github.com/sumneko/lua-language-server/issues/542) -* `FIX` [#543](https://github.com/sumneko/lua-language-server/issues/543) -* `FIX` [#553](https://github.com/sumneko/lua-language-server/issues/553) -* `FIX` [#562](https://github.com/sumneko/lua-language-server/issues/562) -* `FIX` [#563](https://github.com/sumneko/lua-language-server/issues/563) +* `FIX` [#441](https://github.com/LuaLS/lua-language-server/issues/441) +* `FIX` [#493](https://github.com/LuaLS/lua-language-server/issues/493) +* `FIX` [#531](https://github.com/LuaLS/lua-language-server/issues/531) +* `FIX` [#542](https://github.com/LuaLS/lua-language-server/issues/542) +* `FIX` [#543](https://github.com/LuaLS/lua-language-server/issues/543) +* `FIX` [#553](https://github.com/LuaLS/lua-language-server/issues/553) +* `FIX` [#562](https://github.com/LuaLS/lua-language-server/issues/562) +* `FIX` [#563](https://github.com/LuaLS/lua-language-server/issues/563) ## 1.21.3 `2021-6-17` @@ -897,14 +1461,14 @@ ## 1.21.2 `2021-5-18` * `FIX` loaded new file with ignored filename -* `FIX` [#536](https://github.com/sumneko/lua-language-server/issues/536) -* `FIX` [#537](https://github.com/sumneko/lua-language-server/issues/537) -* `FIX` [#538](https://github.com/sumneko/lua-language-server/issues/538) -* `FIX` [#539](https://github.com/sumneko/lua-language-server/issues/539) +* `FIX` [#536](https://github.com/LuaLS/lua-language-server/issues/536) +* `FIX` [#537](https://github.com/LuaLS/lua-language-server/issues/537) +* `FIX` [#538](https://github.com/LuaLS/lua-language-server/issues/538) +* `FIX` [#539](https://github.com/LuaLS/lua-language-server/issues/539) ## 1.21.1 `2021-5-8` -* `FIX` [#529](https://github.com/sumneko/lua-language-server/issues/529) +* `FIX` [#529](https://github.com/LuaLS/lua-language-server/issues/529) ## 1.21.0 `2021-5-7` @@ -918,47 +1482,47 @@ * `NEW` setting: `hover.enumsLimit` * `CHG` folding: supports `-- #region` * `FIX` completion: details may be suspended -* `FIX` [#522](https://github.com/sumneko/lua-language-server/issues/522) -* `FIX` [#523](https://github.com/sumneko/lua-language-server/issues/523) +* `FIX` [#522](https://github.com/LuaLS/lua-language-server/issues/522) +* `FIX` [#523](https://github.com/LuaLS/lua-language-server/issues/523) ## 1.20.4 `2021-4-13` * `NEW` diagnostic: `deprecated` -* `FIX` [#464](https://github.com/sumneko/lua-language-server/issues/464) -* `FIX` [#497](https://github.com/sumneko/lua-language-server/issues/497) -* `FIX` [#502](https://github.com/sumneko/lua-language-server/issues/502) +* `FIX` [#464](https://github.com/LuaLS/lua-language-server/issues/464) +* `FIX` [#497](https://github.com/LuaLS/lua-language-server/issues/497) +* `FIX` [#502](https://github.com/LuaLS/lua-language-server/issues/502) ## 1.20.3 `2021-4-6` -* `FIX` [#479](https://github.com/sumneko/lua-language-server/issues/479) -* `FIX` [#483](https://github.com/sumneko/lua-language-server/issues/483) -* `FIX` [#485](https://github.com/sumneko/lua-language-server/issues/485) -* `FIX` [#487](https://github.com/sumneko/lua-language-server/issues/487) -* `FIX` [#488](https://github.com/sumneko/lua-language-server/issues/488) -* `FIX` [#490](https://github.com/sumneko/lua-language-server/issues/490) -* `FIX` [#495](https://github.com/sumneko/lua-language-server/issues/495) +* `FIX` [#479](https://github.com/LuaLS/lua-language-server/issues/479) +* `FIX` [#483](https://github.com/LuaLS/lua-language-server/issues/483) +* `FIX` [#485](https://github.com/LuaLS/lua-language-server/issues/485) +* `FIX` [#487](https://github.com/LuaLS/lua-language-server/issues/487) +* `FIX` [#488](https://github.com/LuaLS/lua-language-server/issues/488) +* `FIX` [#490](https://github.com/LuaLS/lua-language-server/issues/490) +* `FIX` [#495](https://github.com/LuaLS/lua-language-server/issues/495) ## 1.20.2 `2021-4-2` * `CHG` `LuaDoc`: supports `---@param self TYPE` * `CHG` completion: does not show suggests after `\n`, `{` and `,`, unless your setting `editor.acceptSuggestionOnEnter` is `off` -* `FIX` [#482](https://github.com/sumneko/lua-language-server/issues/482) +* `FIX` [#482](https://github.com/LuaLS/lua-language-server/issues/482) ## 1.20.1 `2021-3-27` * `FIX` telemetry window blocks initializing -* `FIX` [#468](https://github.com/sumneko/lua-language-server/issues/468) +* `FIX` [#468](https://github.com/LuaLS/lua-language-server/issues/468) ## 1.20.0 `2021-3-27` -* `CHG` telemetry: change to opt-in, see [#462](https://github.com/sumneko/lua-language-server/issues/462) and [Privacy-Policy](https://github.com/sumneko/lua-language-server/wiki/Privacy-Policy) -* `FIX` [#467](https://github.com/sumneko/lua-language-server/issues/467) +* `CHG` telemetry: change to opt-in, see [#462](https://github.com/LuaLS/lua-language-server/issues/462) and [Privacy-Policy](https://luals.github.io/privacy/#language-server) +* `FIX` [#467](https://github.com/LuaLS/lua-language-server/issues/467) ## 1.19.1 `2021-3-22` * `CHG` improve performance -* `FIX` [#457](https://github.com/sumneko/lua-language-server/issues/457) -* `FIX` [#458](https://github.com/sumneko/lua-language-server/issues/458) +* `FIX` [#457](https://github.com/LuaLS/lua-language-server/issues/457) +* `FIX` [#458](https://github.com/LuaLS/lua-language-server/issues/458) ## 1.19.0 `2021-3-18` @@ -1005,13 +1569,13 @@ f( -- view comments of `1` and `2` in completion * `CHG` improve initialization speed * `CHG` improve performance * `FIX` missed syntax error `function m['x']() end` -* `FIX` [#452](https://github.com/sumneko/lua-language-server/issues/452) +* `FIX` [#452](https://github.com/LuaLS/lua-language-server/issues/452) ## 1.18.1 `2021-3-10` * `CHG` semantic-tokens: improve colors of `const` and `close` * `CHG` type-formating: improve execution conditions -* `FIX` [#444](https://github.com/sumneko/lua-language-server/issues/444) +* `FIX` [#444](https://github.com/LuaLS/lua-language-server/issues/444) ## 1.18.0 `2021-3-9` @@ -1038,21 +1602,21 @@ f( -- view comments of `1` and `2` in completion * `CHG` `Lua.workspace.library`: use `path[]` instead of `` * `FIX` missed syntax error `local a = 1` * `FIX` workspace: preload blocked when hitting `Lua.workspace.maxPreload` -* `FIX` [#443](https://github.com/sumneko/lua-language-server/issues/443) -* `FIX` [#445](https://github.com/sumneko/lua-language-server/issues/445) +* `FIX` [#443](https://github.com/LuaLS/lua-language-server/issues/443) +* `FIX` [#445](https://github.com/LuaLS/lua-language-server/issues/445) ## 1.17.4 `2021-3-4` -* `FIX` [#437](https://github.com/sumneko/lua-language-server/issues/437) again -* `FIX` [#438](https://github.com/sumneko/lua-language-server/issues/438) +* `FIX` [#437](https://github.com/LuaLS/lua-language-server/issues/437) again +* `FIX` [#438](https://github.com/LuaLS/lua-language-server/issues/438) ## 1.17.3 `2021-3-3` * `CHG` intelli-scense: treat `V[]` as `table` in `pairs` * `FIX` completion: `detail` disappears during continuous input -* `FIX` [#435](https://github.com/sumneko/lua-language-server/issues/435) -* `FIX` [#436](https://github.com/sumneko/lua-language-server/issues/436) -* `FIX` [#437](https://github.com/sumneko/lua-language-server/issues/437) +* `FIX` [#435](https://github.com/LuaLS/lua-language-server/issues/435) +* `FIX` [#436](https://github.com/LuaLS/lua-language-server/issues/436) +* `FIX` [#437](https://github.com/LuaLS/lua-language-server/issues/437) ## 1.17.2 `2021-3-2` @@ -1063,9 +1627,9 @@ f( -- view comments of `1` and `2` in completion * `CHG` intelli-scense: improve infer across `table` and `V[]`. * `CHG` intelli-scense: improve infer across `pairs` and `ipairs` * `FIX` hover: shows nothing when hovering unknown function -* `FIX` [#398](https://github.com/sumneko/lua-language-server/issues/398) -* `FIX` [#421](https://github.com/sumneko/lua-language-server/issues/421) -* `FIX` [#422](https://github.com/sumneko/lua-language-server/issues/422) +* `FIX` [#398](https://github.com/LuaLS/lua-language-server/issues/398) +* `FIX` [#421](https://github.com/LuaLS/lua-language-server/issues/421) +* `FIX` [#422](https://github.com/LuaLS/lua-language-server/issues/422) ## 1.17.0 `2021-2-24` @@ -1074,69 +1638,69 @@ f( -- view comments of `1` and `2` in completion * `CHG` completion: improve field and table * `CHG` improve infer cross `ipairs` * `CHG` cache globals when loading -* `CHG` completion: remove trigger character `\n` for now, see [#401](https://github.com/sumneko/lua-language-server/issues/401) +* `CHG` completion: remove trigger character `\n` for now, see [#401](https://github.com/LuaLS/lua-language-server/issues/401) * `FIX` diagnositc: may open file with wrong uri case -* `FIX` [#406](https://github.com/sumneko/lua-language-server/issues/406) +* `FIX` [#406](https://github.com/LuaLS/lua-language-server/issues/406) ## 1.16.1 `2021-2-22` * `FIX` signature: parameters may be misplaced * `FIX` completion: interface in nested table * `FIX` completion: interface not show after `,` -* `FIX` [#400](https://github.com/sumneko/lua-language-server/issues/400) -* `FIX` [#402](https://github.com/sumneko/lua-language-server/issues/402) -* `FIX` [#403](https://github.com/sumneko/lua-language-server/issues/403) -* `FIX` [#404](https://github.com/sumneko/lua-language-server/issues/404) +* `FIX` [#400](https://github.com/LuaLS/lua-language-server/issues/400) +* `FIX` [#402](https://github.com/LuaLS/lua-language-server/issues/402) +* `FIX` [#403](https://github.com/LuaLS/lua-language-server/issues/403) +* `FIX` [#404](https://github.com/LuaLS/lua-language-server/issues/404) * `FIX` runtime errors ## 1.16.0 `2021-2-20` * `NEW` file encoding supports `ansi` -* `NEW` completion: supports interface, see [#384](https://github.com/sumneko/lua-language-server/issues/384) +* `NEW` completion: supports interface, see [#384](https://github.com/LuaLS/lua-language-server/issues/384) * `NEW` `LuaDoc`: supports multiple class inheritance: `---@class Food: Burger, Pizza, Pie, Pasta` * `CHG` rename `table*` to `tablelib` -* `CHG` `LuaDoc`: revert compatible with `--@`, see [#392](https://github.com/sumneko/lua-language-server/issues/392) +* `CHG` `LuaDoc`: revert compatible with `--@`, see [#392](https://github.com/LuaLS/lua-language-server/issues/392) * `CHG` improve performance * `FIX` missed syntax error `f() = 1` * `FIX` missed global `bit` in `LuaJIT` * `FIX` completion: may insert error text when continuous inputing * `FIX` completion: may insert error text after resolve -* `FIX` [#349](https://github.com/sumneko/lua-language-server/issues/349) -* `FIX` [#396](https://github.com/sumneko/lua-language-server/issues/396) +* `FIX` [#349](https://github.com/LuaLS/lua-language-server/issues/349) +* `FIX` [#396](https://github.com/LuaLS/lua-language-server/issues/396) ## 1.15.1 `2021-2-18` * `CHG` diagnostic: `unused-local` excludes `doc.param` -* `CHG` definition: excludes values, see [#391](https://github.com/sumneko/lua-language-server/issues/391) +* `CHG` definition: excludes values, see [#391](https://github.com/LuaLS/lua-language-server/issues/391) * `FIX` not works on Linux and macOS ## 1.15.0 `2021-2-9` * `NEW` LUNAR YEAR, BE HAPPY! * `CHG` diagnostic: when there are too many errors, the main errors will be displayed first -* `CHG` main thread no longer loop sleeps, see [#329](https://github.com/sumneko/lua-language-server/issues/329) [#386](https://github.com/sumneko/lua-language-server/issues/386) +* `CHG` main thread no longer loop sleeps, see [#329](https://github.com/LuaLS/lua-language-server/issues/329) [#386](https://github.com/LuaLS/lua-language-server/issues/386) * `CHG` improve performance ## 1.14.3 `2021-2-8` -* `CHG` hint: disabled by default, see [#380](https://github.com/sumneko/lua-language-server/issues/380) -* `FIX` [#381](https://github.com/sumneko/lua-language-server/issues/381) -* `FIX` [#382](https://github.com/sumneko/lua-language-server/issues/382) -* `FIX` [#388](https://github.com/sumneko/lua-language-server/issues/388) +* `CHG` hint: disabled by default, see [#380](https://github.com/LuaLS/lua-language-server/issues/380) +* `FIX` [#381](https://github.com/LuaLS/lua-language-server/issues/381) +* `FIX` [#382](https://github.com/LuaLS/lua-language-server/issues/382) +* `FIX` [#388](https://github.com/LuaLS/lua-language-server/issues/388) ## 1.14.2 `2021-2-4` -* `FIX` [#356](https://github.com/sumneko/lua-language-server/issues/356) -* `FIX` [#375](https://github.com/sumneko/lua-language-server/issues/375) -* `FIX` [#376](https://github.com/sumneko/lua-language-server/issues/376) -* `FIX` [#377](https://github.com/sumneko/lua-language-server/issues/377) -* `FIX` [#378](https://github.com/sumneko/lua-language-server/issues/378) -* `FIX` [#379](https://github.com/sumneko/lua-language-server/issues/379) +* `FIX` [#356](https://github.com/LuaLS/lua-language-server/issues/356) +* `FIX` [#375](https://github.com/LuaLS/lua-language-server/issues/375) +* `FIX` [#376](https://github.com/LuaLS/lua-language-server/issues/376) +* `FIX` [#377](https://github.com/LuaLS/lua-language-server/issues/377) +* `FIX` [#378](https://github.com/LuaLS/lua-language-server/issues/378) +* `FIX` [#379](https://github.com/LuaLS/lua-language-server/issues/379) * `FIX` a lot of runtime errors ## 1.14.1 `2021-2-2` -* `FIX` [#372](https://github.com/sumneko/lua-language-server/issues/372) +* `FIX` [#372](https://github.com/LuaLS/lua-language-server/issues/372) ## 1.14.0 `2021-2-2` @@ -1146,8 +1710,8 @@ f( -- view comments of `1` and `2` in completion * `CHG` create/delete/rename files no longer reload workspace * `CHG` `LuaDoc`: compatible with `--@` * `FIX` `VSCode` settings -* `FIX` [#368](https://github.com/sumneko/lua-language-server/issues/368) -* `FIX` [#371](https://github.com/sumneko/lua-language-server/issues/371) +* `FIX` [#368](https://github.com/LuaLS/lua-language-server/issues/368) +* `FIX` [#371](https://github.com/LuaLS/lua-language-server/issues/371) ## 1.13.0 `2021-1-28` @@ -1169,15 +1733,15 @@ f( -- view comments of `1` and `2` in completion ## 1.12.0 `2021-1-26` * `NEW` progress -* `NEW` [#340](https://github.com/sumneko/lua-language-server/pull/340): supports `---@type table` -* `FIX` [#355](https://github.com/sumneko/lua-language-server/pull/355) -* `FIX` [#359](https://github.com/sumneko/lua-language-server/issues/359) -* `FIX` [#361](https://github.com/sumneko/lua-language-server/issues/361) +* `NEW` [#340](https://github.com/LuaLS/lua-language-server/pull/340): supports `---@type table` +* `FIX` [#355](https://github.com/LuaLS/lua-language-server/pull/355) +* `FIX` [#359](https://github.com/LuaLS/lua-language-server/issues/359) +* `FIX` [#361](https://github.com/LuaLS/lua-language-server/issues/361) ## 1.11.2 `2021-1-7` -* `FIX` [#345](https://github.com/sumneko/lua-language-server/issues/345): not works with unexpect args -* `FIX` [#346](https://github.com/sumneko/lua-language-server/issues/346): dont modify the cache +* `FIX` [#345](https://github.com/LuaLS/lua-language-server/issues/345): not works with unexpect args +* `FIX` [#346](https://github.com/LuaLS/lua-language-server/issues/346): dont modify the cache ## 1.11.1 `2021-1-5` @@ -1202,7 +1766,7 @@ f( -- view comments of `1` and `2` in completion * `CHG` completion: show words in string * `CHG` completion: split `for .. in` to `for .. ipairs` and `for ..pairs` * `CHG` diagnostic: `unused-function` checks recursive -* `FIX` [#339](https://github.com/sumneko/lua-language-server/issues/339) +* `FIX` [#339](https://github.com/LuaLS/lua-language-server/issues/339) ## 1.9.0 `2020-12-31` @@ -1226,7 +1790,7 @@ f( -- view comments of `1` and `2` in completion `2020-12-23` * `NEW` runtime: support nonstandard symbol * `NEW` diagnostic: `close-non-object` -* `FIX` [#318](https://github.com/sumneko/lua-language-server/issues/318) +* `FIX` [#318](https://github.com/LuaLS/lua-language-server/issues/318) ## 1.7.4 `2020-12-20` @@ -1235,12 +1799,12 @@ f( -- view comments of `1` and `2` in completion ## 1.7.3 `2020-12-20` * `FIX` luadoc: typo of `package.config` -* `FIX` [#310](https://github.com/sumneko/lua-language-server/issues/310) +* `FIX` [#310](https://github.com/LuaLS/lua-language-server/issues/310) ## 1.7.2 `2020-12-17` * `CHG` completion: use custom tabsize -* `FIX` [#307](https://github.com/sumneko/lua-language-server/issues/307) +* `FIX` [#307](https://github.com/LuaLS/lua-language-server/issues/307) * `FIX` a lot of runtime errors ## 1.7.1 @@ -1254,16 +1818,16 @@ f( -- view comments of `1` and `2` in completion `2020-12-16` * `NEW` diagnostic: `undefined-field` * `NEW` telemetry: - + [What data will be sent](https://github.com/sumneko/lua-language-server/blob/master/script/service/telemetry.lua) - + [How to use this data](https://github.com/sumneko/lua-telemetry-server/tree/master/method) + + [What data will be sent](https://github.com/LuaLS/lua-language-server/blob/master/script/service/telemetry.lua) + + [How to use this data](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) * `CHG` diagnostic: `unused-function` ignores function with `` * `CHG` semantic: not cover local call * `CHG` language client: update to [7.0.0](https://github.com/microsoft/vscode-languageserver-node/commit/20681d7632bb129def0c751be73cf76bd01f2f3a) * `FIX` semantic: tokens may not be updated correctly * `FIX` completion: require path broken * `FIX` hover: document uri -* `FIX` [#291](https://github.com/sumneko/lua-language-server/issues/291) -* `FIX` [#294](https://github.com/sumneko/lua-language-server/issues/294) +* `FIX` [#291](https://github.com/LuaLS/lua-language-server/issues/291) +* `FIX` [#294](https://github.com/LuaLS/lua-language-server/issues/294) ## 1.6.0 `2020-12-14` @@ -1275,16 +1839,16 @@ f( -- view comments of `1` and `2` in completion * `CHG` hover: `LuaDoc` also catchs `--` (no need `---`) * `CHG` rename: support doc * `CHG` completion: keyword considers expression -* `FIX` [#297](https://github.com/sumneko/lua-language-server/issues/297) +* `FIX` [#297](https://github.com/LuaLS/lua-language-server/issues/297) ## 1.5.0 `2020-12-5` * `NEW` setting `runtime.unicodeName` * `NEW` fully supports `---@generic T` -* `FIX` [#274](https://github.com/sumneko/lua-language-server/issues/274) -* `FIX` [#276](https://github.com/sumneko/lua-language-server/issues/276) -* `FIX` [#279](https://github.com/sumneko/lua-language-server/issues/279) -* `FIX` [#280](https://github.com/sumneko/lua-language-server/issues/280) +* `FIX` [#274](https://github.com/LuaLS/lua-language-server/issues/274) +* `FIX` [#276](https://github.com/LuaLS/lua-language-server/issues/276) +* `FIX` [#279](https://github.com/LuaLS/lua-language-server/issues/279) +* `FIX` [#280](https://github.com/LuaLS/lua-language-server/issues/280) ## 1.4.0 `2020-12-3` @@ -1310,12 +1874,12 @@ f( -- view comments of `1` and `2` in completion ## 1.2.1 `2020-11-27` -* `FIX` syntaxes tokens: [#272](https://github.com/sumneko/lua-language-server/issues/272) +* `FIX` syntaxes tokens: [#272](https://github.com/LuaLS/lua-language-server/issues/272) ## 1.2.0 `2020-11-27` -* `NEW` hover shows comments from `---@param` and `---@return`: [#135](https://github.com/sumneko/lua-language-server/issues/135) +* `NEW` hover shows comments from `---@param` and `---@return`: [#135](https://github.com/LuaLS/lua-language-server/issues/135) * `NEW` support `LuaDoc` as tail comment * `FIX` `---@class` inheritance * `FIX` missed syntaxes token: `punctuation.definition.parameters.finish.lua` @@ -1323,12 +1887,12 @@ f( -- view comments of `1` and `2` in completion ## 1.1.4 `2020-11-25` -* `FIX` wiered completion suggests for require paths in `Linux` and `macOS`: [#269](https://github.com/sumneko/lua-language-server/issues/269) +* `FIX` wiered completion suggests for require paths in `Linux` and `macOS`: [#269](https://github.com/LuaLS/lua-language-server/issues/269) ## 1.1.3 `2020-11-25` -* `FIX` extension not works in `Ubuntu`: [#268](https://github.com/sumneko/lua-language-server/issues/268) +* `FIX` extension not works in `Ubuntu`: [#268](https://github.com/LuaLS/lua-language-server/issues/268) ## 1.1.2 `2020-11-24` @@ -1339,7 +1903,7 @@ f( -- view comments of `1` and `2` in completion * `CHG` `LuaDoc` supports `--- @` * `CHG` `find reference` uses extra `Lua.intelliSense.searchDepth` * `CHG` diagnostics are limited by `100` in each file -* `FIX` library files are under limit of `Lua.workspace.maxPreload`: [#266](https://github.com/sumneko/lua-language-server/issues/266) +* `FIX` library files are under limit of `Lua.workspace.maxPreload`: [#266](https://github.com/LuaLS/lua-language-server/issues/266) ## 1.1.1 `2020-11-23` @@ -1364,9 +1928,9 @@ f( -- view comments of `1` and `2` in completion * `CHG` optimize performance * `CHG` updated language client * `CHG` `unused-function` ignores global functions (may used out of Lua) -* `CHG` checks if client supports `Lua.completion.enable`: [#259](https://github.com/sumneko/lua-language-server/issues/259) +* `CHG` checks if client supports `Lua.completion.enable`: [#259](https://github.com/LuaLS/lua-language-server/issues/259) * `FIX` support for single Lua file -* `FIX` [#257](https://github.com/sumneko/lua-language-server/issues/257) +* `FIX` [#257](https://github.com/LuaLS/lua-language-server/issues/257) ## 1.0.5 `2020-11-14` @@ -1383,7 +1947,7 @@ f( -- view comments of `1` and `2` in completion * `NEW` server kills itself when disconnecting * `NEW` `LuaDoc` supports more `EmmyLua` -* `FIX` `Lua.diagnostics.enable` not works: [#251](https://github.com/sumneko/lua-language-server/issues/251) +* `FIX` `Lua.diagnostics.enable` not works: [#251](https://github.com/LuaLS/lua-language-server/issues/251) ## 1.0.2 `2020-11-11` @@ -1391,7 +1955,7 @@ f( -- view comments of `1` and `2` in completion * `NEW` supports `---|` after `doc.type` * `CHG` `lowcase-global` ignores globals with `---@class` * `FIX` endless loop -* `FIX` [#244](https://github.com/sumneko/lua-language-server/issues/244) +* `FIX` [#244](https://github.com/LuaLS/lua-language-server/issues/244) ## 1.0.1 `2020-11-10` diff --git a/debugger.lua b/debugger.lua index db5b2d5da..49aedaaa8 100644 --- a/debugger.lua +++ b/debugger.lua @@ -51,7 +51,7 @@ local function tryDebugger() local entry = assert(package.searchpath('debugger', debugPath .. path)) local root = debugPath local addr = ("127.0.0.1:%d"):format(DBGPORT) - local dbg = loadfile(entry)(root) + local dbg = loadfile(entry)(entry) dbg:start { address = addr, } diff --git a/doc/en-us/config.md b/doc/en-us/config.md index 719a4a6e5..f57d4d64e 100644 --- a/doc/en-us/config.md +++ b/doc/en-us/config.md @@ -1,3 +1,35 @@ +# addonManager.enable + +Whether the addon manager is enabled or not. + +## type + +```ts +boolean +``` + +## default + +```jsonc +true +``` + +# codeLens.enable + +Enable code lens. + +## type + +```ts +boolean +``` + +## default + +```jsonc +false +``` + # completion.autoRequire When the input looks like a file name, automatically `require` this file. @@ -190,6 +222,7 @@ Array * ``"action-after-return"`` * ``"ambiguity-1"`` +* ``"ambiguous-syntax"`` * ``"args-after-dots"`` * ``"assign-type-mismatch"`` * ``"await-in-sync"`` @@ -221,12 +254,41 @@ Array * ``"err-nonstandard-symbol"`` * ``"err-then-as-do"`` * ``"exp-in-action"`` +* ``"global-element"`` * ``"global-in-nil-env"`` +* ``"incomplete-signature-doc"`` * ``"index-in-func-name"`` +* ``"inject-field"`` +* ``"invisible"`` * ``"jump-local-scope"`` * ``"keyword"`` * ``"local-limit"`` * ``"lowercase-global"`` +* ``"lua-doc-miss-sign"`` +* ``"luadoc-error-diag-mode"`` +* ``"luadoc-miss-alias-extends"`` +* ``"luadoc-miss-alias-name"`` +* ``"luadoc-miss-arg-name"`` +* ``"luadoc-miss-cate-name"`` +* ``"luadoc-miss-class-extends-name"`` +* ``"luadoc-miss-class-name"`` +* ``"luadoc-miss-diag-mode"`` +* ``"luadoc-miss-diag-name"`` +* ``"luadoc-miss-field-extends"`` +* ``"luadoc-miss-field-name"`` +* ``"luadoc-miss-fun-after-overload"`` +* ``"luadoc-miss-generic-name"`` +* ``"luadoc-miss-local-name"`` +* ``"luadoc-miss-module-name"`` +* ``"luadoc-miss-operator-name"`` +* ``"luadoc-miss-param-extends"`` +* ``"luadoc-miss-param-name"`` +* ``"luadoc-miss-see-name"`` +* ``"luadoc-miss-sign-name"`` +* ``"luadoc-miss-symbol"`` +* ``"luadoc-miss-type-name"`` +* ``"luadoc-miss-vararg-type"`` +* ``"luadoc-miss-version"`` * ``"malformed-number"`` * ``"miss-end"`` * ``"miss-esc-x"`` @@ -240,10 +302,16 @@ Array * ``"miss-sep-in-table"`` * ``"miss-space-between"`` * ``"miss-symbol"`` +* ``"missing-fields"`` +* ``"missing-global-doc"`` +* ``"missing-local-export-doc"`` * ``"missing-parameter"`` * ``"missing-return"`` * ``"missing-return-value"`` +* ``"name-style-check"`` * ``"need-check-nil"`` +* ``"need-paren"`` +* ``"nesting-long-mark"`` * ``"newfield-call"`` * ``"newline-call"`` * ``"no-unknown"`` @@ -382,10 +450,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -403,6 +476,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -419,6 +495,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -429,6 +506,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -436,6 +514,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -500,10 +579,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -521,6 +605,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -537,6 +624,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -547,6 +635,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -554,6 +643,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -654,42 +744,128 @@ object Enable ambiguous operator precedence diagnostics. For example, the `num or 0 + 1` expression will be suggested `(num or 0) + 1` instead. */ "ambiguity-1": "Any", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Opened", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "None", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Opened", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Opened", "circle-doc-class": "Any", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Any", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Opened", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "None", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Any", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Any", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Any", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Any", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Any", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Any", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Any", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Any", /* Enable duplicate table index diagnostics. */ "duplicate-index": "Any", - "duplicate-set-field": "Any", + /* + Enable diagnostics for setting the same field in a class more than once. + */ + "duplicate-set-field": "Opened", /* Enable empty code block diagnostics. */ "empty-block": "Opened", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "None", + /* Enable cannot use global variables ๏ผˆ `_ENV` is set to `nil`๏ผ‰ diagnostics. */ "global-in-nil-env": "Any", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "None", + "inject-field": "Opened", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Any", + /* Enable lowercase global variable definition diagnostics. */ "lowercase-global": "Any", + "missing-fields": "Any", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "None", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "None", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Any", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Any", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Any", + /* + Enable diagnostics for name style. + */ + "name-style-check": "None", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Opened", /* Enable newfield call diagnostics. It is raised when the parenthesis of a function call appear on the following line when defining a field in a table. @@ -699,8 +875,17 @@ object Enable newline call diagnostics. Is's raised when a line starting with `(` is encountered, which is syntactically parsed as a function call on the previous line. */ "newline-call": "Any", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "None", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "None", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Opened", /* Enable redefined local variable diagnostics. @@ -710,34 +895,73 @@ object Enable redundant function parameter diagnostics. */ "redundant-parameter": "Any", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Opened", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Any", /* Enable the redundant values assigned diagnostics. It's raised during assignment operation, when the number of values is higher than the number of objects being assigned. */ "redundant-value": "Any", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Opened", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "None", /* Enable trailing space diagnostics. */ "trailing-space": "Opened", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Any", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Any", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Any", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Any", /* Enable undefined environment variable diagnostics. It's raised when `_ENV` table is set to a new literal table, but the used global variable is no longer present in the global environment. */ "undefined-env-child": "Any", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Opened", /* Enable undefined global variable diagnostics. */ "undefined-global": "Any", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Any", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Any", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Any", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Opened", /* Enable unused function diagnostics. @@ -790,42 +1014,128 @@ object Enable ambiguous operator precedence diagnostics. For example, the `num or 0 + 1` expression will be suggested `(num or 0) + 1` instead. */ "ambiguity-1": "Warning", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Warning", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "Warning", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Warning", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Warning", "circle-doc-class": "Warning", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Warning", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Hint", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "Warning", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Warning", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Warning", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Warning", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Warning", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Warning", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Warning", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Warning", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Warning", /* Enable duplicate table index diagnostics. */ "duplicate-index": "Warning", + /* + Enable diagnostics for setting the same field in a class more than once. + */ "duplicate-set-field": "Warning", /* Enable empty code block diagnostics. */ "empty-block": "Hint", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "Warning", + /* Enable cannot use global variables ๏ผˆ `_ENV` is set to `nil`๏ผ‰ diagnostics. */ "global-in-nil-env": "Warning", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "Warning", + "inject-field": "Warning", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Warning", + /* Enable lowercase global variable definition diagnostics. */ "lowercase-global": "Information", + "missing-fields": "Warning", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "Warning", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "Warning", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Warning", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Warning", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Warning", + /* + Enable diagnostics for name style. + */ + "name-style-check": "Warning", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Warning", /* Enable newfield call diagnostics. It is raised when the parenthesis of a function call appear on the following line when defining a field in a table. @@ -835,8 +1145,17 @@ object Enable newline call diagnostics. Is's raised when a line starting with `(` is encountered, which is syntactically parsed as a function call on the previous line. */ "newline-call": "Warning", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "Warning", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "Warning", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Warning", /* Enable redefined local variable diagnostics. @@ -846,34 +1165,73 @@ object Enable redundant function parameter diagnostics. */ "redundant-parameter": "Warning", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Hint", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Warning", /* Enable the redundant values assigned diagnostics. It's raised during assignment operation, when the number of values is higher than the number of objects being assigned. */ "redundant-value": "Warning", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Warning", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "Information", /* Enable trailing space diagnostics. */ "trailing-space": "Hint", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Warning", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Warning", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Warning", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Warning", /* Enable undefined environment variable diagnostics. It's raised when `_ENV` table is set to a new literal table, but the used global variable is no longer present in the global environment. */ "undefined-env-child": "Information", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Warning", /* Enable undefined global variable diagnostics. */ "undefined-global": "Warning", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Warning", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Warning", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Warning", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Hint", /* Enable unused function diagnostics. @@ -912,7 +1270,7 @@ Array # diagnostics.workspaceDelay -Latency (milliseconds) for workspace diagnostics. When you start the workspace, or edit any file, the entire workspace will be re-diagnosed in the background. Set to negative to disable workspace diagnostics. +Latency (milliseconds) for workspace diagnostics. ## type @@ -926,6 +1284,28 @@ integer 3000 ``` +# diagnostics.workspaceEvent + +Set the time to trigger workspace diagnostics. + +## type + +```ts +string +``` + +## enum + +* ``"OnChange"``: Trigger workspace diagnostics when the file is changed. +* ``"OnSave"``: Trigger workspace diagnostics when the file is saved. +* ``"None"``: Disable workspace diagnostics. + +## default + +```jsonc +"OnSave" +``` + # diagnostics.workspaceRate Workspace diagnostics run rate (%). Decreasing this value reduces CPU usage, but also reduces the speed of workspace diagnostics. The diagnosis of the file you are currently editing is always done at full speed and is not affected by this setting. @@ -942,6 +1322,54 @@ integer 100 ``` +# doc.packageName + +Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.privateName + +Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.protectedName + +Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # format.defaultConfig The default format configuration. Has a lower priority than `.editorconfig` file in the workspace. @@ -1219,9 +1647,25 @@ integer 1000 ``` +# misc.executablePath + +Specify the executable path in VSCode. + +## type + +```ts +string +``` + +## default + +```jsonc +"" +``` + # misc.parameters -[Command line parameters](https://github.com/sumneko/lua-telemetry-server/tree/master/method) when starting the language service in VSCode. +[Command line parameters](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) when starting the language server in VSCode. ## type @@ -1235,6 +1679,22 @@ Array [] ``` +# nameStyle.config + +Set name style config + +## type + +```ts +Object +``` + +## default + +```jsonc +{} +``` + # runtime.builtin Adjust the enabled state of the built-in library. You can disable (or redefine) the non-existent library according to the actual runtime environment. @@ -1269,10 +1729,13 @@ object "ffi": "default", "io": "default", "jit": "default", + "jit.profile": "default", + "jit.util": "default", "math": "default", "os": "default", "package": "default", "string": "default", + "string.buffer": "default", "table": "default", "table.clear": "default", "table.new": "default", @@ -1395,7 +1858,7 @@ false # runtime.plugin -Plugin path. Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/Plugins) to learn more. +Plugin path. Please read [wiki](https://luals.github.io/wiki/plugins) to learn more. ## type @@ -1584,23 +2047,6 @@ Array [] ``` -# telemetry.enable - -Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://github.com/sumneko/lua-language-server/wiki/Home#privacy). - - -## type - -```ts -boolean | null -``` - -## default - -```jsonc -null -``` - # type.castNumberToInteger Allowed to assign the `number` type to the `integer` type. @@ -1655,6 +2101,35 @@ boolean false ``` +# typeFormat.config + +Configures the formatting behavior while typing Lua code. + +## type + +```ts +object +``` + +## default + +```jsonc +{ + /* + Controls if `end` is automatically completed at suitable positions. + */ + "auto_complete_end": "true", + /* + Controls if a separator is automatically appended at the end of a table declaration. + */ + "auto_complete_table_sep": "true", + /* + Controls if a line is formatted at all. + */ + "format_line": "true" +} +``` + # window.progressBar Show progress bar in status bar. @@ -1702,13 +2177,20 @@ Automatic detection and adaptation of third-party libraries, currently supported ## type ```ts -boolean +string ``` +## enum + +* ``"Ask"`` +* ``"Apply"`` +* ``"ApplyInMemory"`` +* ``"Disable"`` + ## default ```jsonc -true +"Ask" ``` # workspace.ignoreDir @@ -1791,22 +2273,6 @@ integer 500 ``` -# workspace.supportScheme - -Provide language server for the Lua files of the following scheme. - -## type - -```ts -Array -``` - -## default - -```jsonc -["file","untitled","git"] -``` - # workspace.useGitIgnore Ignore files list in `.gitignore` . @@ -1825,7 +2291,7 @@ true # workspace.userThirdParty -Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd) +Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd) ## type diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md index ddd613fb5..7add2c982 100644 --- a/doc/pt-br/config.md +++ b/doc/pt-br/config.md @@ -1,3 +1,35 @@ +# addonManager.enable + +Whether the addon manager is enabled or not. + +## type + +```ts +boolean +``` + +## default + +```jsonc +true +``` + +# codeLens.enable + +Enable code lens. + +## type + +```ts +boolean +``` + +## default + +```jsonc +false +``` + # completion.autoRequire When the input looks like a file name, automatically `require` this file. @@ -190,6 +222,7 @@ Array * ``"action-after-return"`` * ``"ambiguity-1"`` +* ``"ambiguous-syntax"`` * ``"args-after-dots"`` * ``"assign-type-mismatch"`` * ``"await-in-sync"`` @@ -221,12 +254,41 @@ Array * ``"err-nonstandard-symbol"`` * ``"err-then-as-do"`` * ``"exp-in-action"`` +* ``"global-element"`` * ``"global-in-nil-env"`` +* ``"incomplete-signature-doc"`` * ``"index-in-func-name"`` +* ``"inject-field"`` +* ``"invisible"`` * ``"jump-local-scope"`` * ``"keyword"`` * ``"local-limit"`` * ``"lowercase-global"`` +* ``"lua-doc-miss-sign"`` +* ``"luadoc-error-diag-mode"`` +* ``"luadoc-miss-alias-extends"`` +* ``"luadoc-miss-alias-name"`` +* ``"luadoc-miss-arg-name"`` +* ``"luadoc-miss-cate-name"`` +* ``"luadoc-miss-class-extends-name"`` +* ``"luadoc-miss-class-name"`` +* ``"luadoc-miss-diag-mode"`` +* ``"luadoc-miss-diag-name"`` +* ``"luadoc-miss-field-extends"`` +* ``"luadoc-miss-field-name"`` +* ``"luadoc-miss-fun-after-overload"`` +* ``"luadoc-miss-generic-name"`` +* ``"luadoc-miss-local-name"`` +* ``"luadoc-miss-module-name"`` +* ``"luadoc-miss-operator-name"`` +* ``"luadoc-miss-param-extends"`` +* ``"luadoc-miss-param-name"`` +* ``"luadoc-miss-see-name"`` +* ``"luadoc-miss-sign-name"`` +* ``"luadoc-miss-symbol"`` +* ``"luadoc-miss-type-name"`` +* ``"luadoc-miss-vararg-type"`` +* ``"luadoc-miss-version"`` * ``"malformed-number"`` * ``"miss-end"`` * ``"miss-esc-x"`` @@ -240,10 +302,16 @@ Array * ``"miss-sep-in-table"`` * ``"miss-space-between"`` * ``"miss-symbol"`` +* ``"missing-fields"`` +* ``"missing-global-doc"`` +* ``"missing-local-export-doc"`` * ``"missing-parameter"`` * ``"missing-return"`` * ``"missing-return-value"`` +* ``"name-style-check"`` * ``"need-check-nil"`` +* ``"need-paren"`` +* ``"nesting-long-mark"`` * ``"newfield-call"`` * ``"newline-call"`` * ``"no-unknown"`` @@ -382,10 +450,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -403,6 +476,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -419,6 +495,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -429,6 +506,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -436,6 +514,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -500,10 +579,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -521,6 +605,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -537,6 +624,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -547,6 +635,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -554,6 +643,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -654,42 +744,128 @@ object ไผ˜ๅ…ˆ็บงๆญงไน‰๏ผŒๅฆ‚๏ผš`num or 0 + 1`๏ผŒๆŽจๆต‹็”จๆˆท็š„ๅฎž้™…ๆœŸๆœ›ไธบ `(num or 0) + 1` */ "ambiguity-1": "Any", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Opened", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "None", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Opened", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Opened", "circle-doc-class": "Any", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Any", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Opened", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "None", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Any", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Any", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Any", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Any", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Any", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Any", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Any", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Any", /* ๅœจๅญ—้ข้‡่กจไธญ้‡ๅคๅฎšไน‰ไบ†็ดขๅผ• */ "duplicate-index": "Any", - "duplicate-set-field": "Any", + /* + Enable diagnostics for setting the same field in a class more than once. + */ + "duplicate-set-field": "Opened", /* ็ฉบไปฃ็ ๅ— */ "empty-block": "Opened", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "None", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅฑ€ๅ˜้‡๏ผˆ `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ† `nil`๏ผ‰ */ "global-in-nil-env": "Any", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "None", + "inject-field": "Opened", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Any", + /* ้ฆ–ๅญ—ๆฏๅฐๅ†™็š„ๅ…จๅฑ€ๅ˜้‡ๅฎšไน‰ */ "lowercase-global": "Any", + "missing-fields": "Any", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "None", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "None", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Any", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Any", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Any", + /* + Enable diagnostics for name style. + */ + "name-style-check": "None", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Opened", /* ๅœจๅญ—้ข้‡่กจไธญ๏ผŒ2่กŒไปฃ็ ไน‹้—ด็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -699,8 +875,17 @@ object ไปฅ `(` ๅผ€ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๆ•ฐ่ฐƒ็”จ */ "newline-call": "Any", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "None", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "None", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Opened", /* ้‡ๅคๅฎšไน‰็š„ๅฑ€้ƒจๅ˜้‡ @@ -710,34 +895,73 @@ object ๅ‡ฝๆ•ฐ่ฐƒ็”จๆ—ถ๏ผŒไผ ๅ…ฅไบ†ๅคšไฝ™็š„ๅ‚ๆ•ฐ */ "redundant-parameter": "Any", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Opened", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Any", /* ่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš */ "redundant-value": "Any", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Opened", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "None", /* ๅŽ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Opened", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Any", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Any", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Any", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Any", /* `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ†ๆ–ฐ็š„ๅญ—้ข้‡่กจ๏ผŒไฝ†ๆ˜ฏ่ฏ•ๅ›พ่Žทๅ–็š„ๅ…จๅฑ€ๅ˜้‡ไธๅ†่ฟ™ๅผ ่กจไธญ */ "undefined-env-child": "Any", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Opened", /* ๆœชๅฎšไน‰็š„ๅ…จๅฑ€ๅ˜้‡ */ "undefined-global": "Any", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Any", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Any", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Any", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Opened", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๆ•ฐ @@ -790,42 +1014,128 @@ object ไผ˜ๅ…ˆ็บงๆญงไน‰๏ผŒๅฆ‚๏ผš`num or 0 + 1`๏ผŒๆŽจๆต‹็”จๆˆท็š„ๅฎž้™…ๆœŸๆœ›ไธบ `(num or 0) + 1` */ "ambiguity-1": "Warning", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Warning", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "Warning", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Warning", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Warning", "circle-doc-class": "Warning", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Warning", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Hint", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "Warning", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Warning", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Warning", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Warning", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Warning", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Warning", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Warning", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Warning", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Warning", /* ๅœจๅญ—้ข้‡่กจไธญ้‡ๅคๅฎšไน‰ไบ†็ดขๅผ• */ "duplicate-index": "Warning", + /* + Enable diagnostics for setting the same field in a class more than once. + */ "duplicate-set-field": "Warning", /* ็ฉบไปฃ็ ๅ— */ "empty-block": "Hint", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "Warning", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅฑ€ๅ˜้‡๏ผˆ `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ† `nil`๏ผ‰ */ "global-in-nil-env": "Warning", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "Warning", + "inject-field": "Warning", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Warning", + /* ้ฆ–ๅญ—ๆฏๅฐๅ†™็š„ๅ…จๅฑ€ๅ˜้‡ๅฎšไน‰ */ "lowercase-global": "Information", + "missing-fields": "Warning", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "Warning", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "Warning", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Warning", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Warning", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Warning", + /* + Enable diagnostics for name style. + */ + "name-style-check": "Warning", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Warning", /* ๅœจๅญ—้ข้‡่กจไธญ๏ผŒ2่กŒไปฃ็ ไน‹้—ด็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -835,8 +1145,17 @@ object ไปฅ `(` ๅผ€ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๆ•ฐ่ฐƒ็”จ */ "newline-call": "Warning", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "Warning", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "Warning", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Warning", /* ้‡ๅคๅฎšไน‰็š„ๅฑ€้ƒจๅ˜้‡ @@ -846,34 +1165,73 @@ object ๅ‡ฝๆ•ฐ่ฐƒ็”จๆ—ถ๏ผŒไผ ๅ…ฅไบ†ๅคšไฝ™็š„ๅ‚ๆ•ฐ */ "redundant-parameter": "Warning", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Hint", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Warning", /* ่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš */ "redundant-value": "Warning", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Warning", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "Information", /* ๅŽ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Hint", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Warning", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Warning", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Warning", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Warning", /* `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ†ๆ–ฐ็š„ๅญ—้ข้‡่กจ๏ผŒไฝ†ๆ˜ฏ่ฏ•ๅ›พ่Žทๅ–็š„ๅ…จๅฑ€ๅ˜้‡ไธๅ†่ฟ™ๅผ ่กจไธญ */ "undefined-env-child": "Information", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Warning", /* ๆœชๅฎšไน‰็š„ๅ…จๅฑ€ๅ˜้‡ */ "undefined-global": "Warning", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Warning", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Warning", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Warning", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Hint", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๆ•ฐ @@ -912,7 +1270,7 @@ Array # diagnostics.workspaceDelay -Latency (milliseconds) for workspace diagnostics. When you start the workspace, or edit any file, the entire workspace will be re-diagnosed in the background. Set to negative to disable workspace diagnostics. +Latency (milliseconds) for workspace diagnostics. ## type @@ -926,6 +1284,28 @@ integer 3000 ``` +# diagnostics.workspaceEvent + +Set the time to trigger workspace diagnostics. + +## type + +```ts +string +``` + +## enum + +* ``"OnChange"``: Trigger workspace diagnostics when the file is changed. +* ``"OnSave"``: Trigger workspace diagnostics when the file is saved. +* ``"None"``: Disable workspace diagnostics. + +## default + +```jsonc +"OnSave" +``` + # diagnostics.workspaceRate Workspace diagnostics run rate (%). Decreasing this value reduces CPU usage, but also reduces the speed of workspace diagnostics. The diagnosis of the file you are currently editing is always done at full speed and is not affected by this setting. @@ -942,6 +1322,54 @@ integer 100 ``` +# doc.packageName + +Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.privateName + +Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.protectedName + +Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # format.defaultConfig The default format configuration. Has a lower priority than `.editorconfig` file in the workspace. @@ -1219,9 +1647,25 @@ integer 1000 ``` +# misc.executablePath + +Specify the executable path in VSCode. + +## type + +```ts +string +``` + +## default + +```jsonc +"" +``` + # misc.parameters -[Command line parameters](https://github.com/sumneko/lua-telemetry-server/tree/master/method) when starting the language service in VSCode. +[Command line parameters](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) when starting the language service in VSCode. ## type @@ -1235,6 +1679,22 @@ Array [] ``` +# nameStyle.config + +Set name style config + +## type + +```ts +Object +``` + +## default + +```jsonc +{} +``` + # runtime.builtin Adjust the enabled state of the built-in library. You can disable (or redefine) the non-existent library according to the actual runtime environment. @@ -1269,10 +1729,13 @@ object "ffi": "default", "io": "default", "jit": "default", + "jit.profile": "default", + "jit.util": "default", "math": "default", "os": "default", "package": "default", "string": "default", + "string.buffer": "default", "table": "default", "table.clear": "default", "table.new": "default", @@ -1395,7 +1858,7 @@ false # runtime.plugin -Plugin path. Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/Plugins) to learn more. +Plugin path. Please read [wiki](https://luals.github.io/wiki/plugins) to learn more. ## type @@ -1584,23 +2047,6 @@ Array [] ``` -# telemetry.enable - -Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://github.com/sumneko/lua-language-server/wiki/Home#privacy). - - -## type - -```ts -boolean | null -``` - -## default - -```jsonc -null -``` - # type.castNumberToInteger Allowed to assign the `number` type to the `integer` type. @@ -1655,6 +2101,35 @@ boolean false ``` +# typeFormat.config + +Configures the formatting behavior while typing Lua code. + +## type + +```ts +object +``` + +## default + +```jsonc +{ + /* + Controls if `end` is automatically completed at suitable positions. + */ + "auto_complete_end": "true", + /* + Controls if a separator is automatically appended at the end of a table declaration. + */ + "auto_complete_table_sep": "true", + /* + Controls if a line is formatted at all. + */ + "format_line": "true" +} +``` + # window.progressBar Show progress bar in status bar. @@ -1702,13 +2177,20 @@ Automatic detection and adaptation of third-party libraries, currently supported ## type ```ts -boolean +string ``` +## enum + +* ``"Ask"`` +* ``"Apply"`` +* ``"ApplyInMemory"`` +* ``"Disable"`` + ## default ```jsonc -true +"Ask" ``` # workspace.ignoreDir @@ -1791,22 +2273,6 @@ integer 500 ``` -# workspace.supportScheme - -Provide language server for the Lua files of the following scheme. - -## type - -```ts -Array -``` - -## default - -```jsonc -["file","untitled","git"] -``` - # workspace.useGitIgnore Ignore files list in `.gitignore` . @@ -1825,7 +2291,7 @@ true # workspace.userThirdParty -Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd) +Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd) ## type diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md index 07d07a743..ebb8325f4 100644 --- a/doc/zh-cn/config.md +++ b/doc/zh-cn/config.md @@ -1,3 +1,35 @@ +# addonManager.enable + +Whether the addon manager is enabled or not. + +## type + +```ts +boolean +``` + +## default + +```jsonc +true +``` + +# codeLens.enable + +ๅฏ็”จไปฃ็ ๅบฆ้‡ใ€‚ + +## type + +```ts +boolean +``` + +## default + +```jsonc +false +``` + # completion.autoRequire ่พ“ๅ…ฅๅ†…ๅฎน็œ‹่ตทๆฅๆ˜ฏไธชๆ–‡ไปถๅๆ—ถ๏ผŒ่‡ชๅŠจ `require` ๆญคๆ–‡ไปถใ€‚ @@ -190,6 +222,7 @@ Array * ``"action-after-return"`` * ``"ambiguity-1"`` +* ``"ambiguous-syntax"`` * ``"args-after-dots"`` * ``"assign-type-mismatch"`` * ``"await-in-sync"`` @@ -221,12 +254,41 @@ Array * ``"err-nonstandard-symbol"`` * ``"err-then-as-do"`` * ``"exp-in-action"`` +* ``"global-element"`` * ``"global-in-nil-env"`` +* ``"incomplete-signature-doc"`` * ``"index-in-func-name"`` +* ``"inject-field"`` +* ``"invisible"`` * ``"jump-local-scope"`` * ``"keyword"`` * ``"local-limit"`` * ``"lowercase-global"`` +* ``"lua-doc-miss-sign"`` +* ``"luadoc-error-diag-mode"`` +* ``"luadoc-miss-alias-extends"`` +* ``"luadoc-miss-alias-name"`` +* ``"luadoc-miss-arg-name"`` +* ``"luadoc-miss-cate-name"`` +* ``"luadoc-miss-class-extends-name"`` +* ``"luadoc-miss-class-name"`` +* ``"luadoc-miss-diag-mode"`` +* ``"luadoc-miss-diag-name"`` +* ``"luadoc-miss-field-extends"`` +* ``"luadoc-miss-field-name"`` +* ``"luadoc-miss-fun-after-overload"`` +* ``"luadoc-miss-generic-name"`` +* ``"luadoc-miss-local-name"`` +* ``"luadoc-miss-module-name"`` +* ``"luadoc-miss-operator-name"`` +* ``"luadoc-miss-param-extends"`` +* ``"luadoc-miss-param-name"`` +* ``"luadoc-miss-see-name"`` +* ``"luadoc-miss-sign-name"`` +* ``"luadoc-miss-symbol"`` +* ``"luadoc-miss-type-name"`` +* ``"luadoc-miss-vararg-type"`` +* ``"luadoc-miss-version"`` * ``"malformed-number"`` * ``"miss-end"`` * ``"miss-esc-x"`` @@ -240,10 +302,16 @@ Array * ``"miss-sep-in-table"`` * ``"miss-space-between"`` * ``"miss-symbol"`` +* ``"missing-fields"`` +* ``"missing-global-doc"`` +* ``"missing-local-export-doc"`` * ``"missing-parameter"`` * ``"missing-return"`` * ``"missing-return-value"`` +* ``"name-style-check"`` * ``"need-check-nil"`` +* ``"need-paren"`` +* ``"nesting-long-mark"`` * ``"newfield-call"`` * ``"newline-call"`` * ``"no-unknown"`` @@ -382,10 +450,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -403,6 +476,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -419,6 +495,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -429,6 +506,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -436,6 +514,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -500,10 +579,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -521,6 +605,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -537,6 +624,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -547,6 +635,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -554,6 +643,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -654,42 +744,128 @@ object ไผ˜ๅ…ˆ็บงๆญงไน‰๏ผŒๅฆ‚๏ผš`num or 0 + 1`๏ผŒๆŽจๆต‹็”จๆˆท็š„ๅฎž้™…ๆœŸๆœ›ไธบ `(num or 0) + 1` */ "ambiguity-1": "Any", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Opened", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "None", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Opened", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Opened", "circle-doc-class": "Any", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Any", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Opened", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "None", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Any", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Any", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Any", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Any", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Any", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Any", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Any", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Any", /* ๅœจๅญ—้ข้‡่กจไธญ้‡ๅคๅฎšไน‰ไบ†็ดขๅผ• */ "duplicate-index": "Any", - "duplicate-set-field": "Any", + /* + Enable diagnostics for setting the same field in a class more than once. + */ + "duplicate-set-field": "Opened", /* ็ฉบไปฃ็ ๅ— */ "empty-block": "Opened", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "None", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅฑ€ๅ˜้‡๏ผˆ `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ† `nil`๏ผ‰ */ "global-in-nil-env": "Any", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "None", + "inject-field": "Opened", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Any", + /* ้ฆ–ๅญ—ๆฏๅฐๅ†™็š„ๅ…จๅฑ€ๅ˜้‡ๅฎšไน‰ */ "lowercase-global": "Any", + "missing-fields": "Any", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "None", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "None", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Any", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Any", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Any", + /* + Enable diagnostics for name style. + */ + "name-style-check": "None", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Opened", /* ๅœจๅญ—้ข้‡่กจไธญ๏ผŒ2่กŒไปฃ็ ไน‹้—ด็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -699,8 +875,17 @@ object ไปฅ `(` ๅผ€ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๆ•ฐ่ฐƒ็”จ */ "newline-call": "Any", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "None", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "None", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Opened", /* ้‡ๅคๅฎšไน‰็š„ๅฑ€้ƒจๅ˜้‡ @@ -710,34 +895,73 @@ object ๅ‡ฝๆ•ฐ่ฐƒ็”จๆ—ถ๏ผŒไผ ๅ…ฅไบ†ๅคšไฝ™็š„ๅ‚ๆ•ฐ */ "redundant-parameter": "Any", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Opened", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Any", /* ่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš */ "redundant-value": "Any", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Opened", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "None", /* ๅŽ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Opened", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Any", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Any", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Any", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Any", /* `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ†ๆ–ฐ็š„ๅญ—้ข้‡่กจ๏ผŒไฝ†ๆ˜ฏ่ฏ•ๅ›พ่Žทๅ–็š„ๅ…จๅฑ€ๅ˜้‡ไธๅ†่ฟ™ๅผ ่กจไธญ */ "undefined-env-child": "Any", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Opened", /* ๆœชๅฎšไน‰็š„ๅ…จๅฑ€ๅ˜้‡ */ "undefined-global": "Any", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Any", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Any", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Any", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Opened", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๆ•ฐ @@ -789,42 +1013,128 @@ object ไผ˜ๅ…ˆ็บงๆญงไน‰๏ผŒๅฆ‚๏ผš`num or 0 + 1`๏ผŒๆŽจๆต‹็”จๆˆท็š„ๅฎž้™…ๆœŸๆœ›ไธบ `(num or 0) + 1` */ "ambiguity-1": "Warning", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Warning", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "Warning", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Warning", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Warning", "circle-doc-class": "Warning", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Warning", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Hint", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "Warning", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Warning", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Warning", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Warning", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Warning", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Warning", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Warning", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Warning", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Warning", /* ๅœจๅญ—้ข้‡่กจไธญ้‡ๅคๅฎšไน‰ไบ†็ดขๅผ• */ "duplicate-index": "Warning", + /* + Enable diagnostics for setting the same field in a class more than once. + */ "duplicate-set-field": "Warning", /* ็ฉบไปฃ็ ๅ— */ "empty-block": "Hint", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "Warning", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅฑ€ๅ˜้‡๏ผˆ `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ† `nil`๏ผ‰ */ "global-in-nil-env": "Warning", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "Warning", + "inject-field": "Warning", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Warning", + /* ้ฆ–ๅญ—ๆฏๅฐๅ†™็š„ๅ…จๅฑ€ๅ˜้‡ๅฎšไน‰ */ "lowercase-global": "Information", + "missing-fields": "Warning", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "Warning", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "Warning", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Warning", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Warning", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Warning", + /* + Enable diagnostics for name style. + */ + "name-style-check": "Warning", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Warning", /* ๅœจๅญ—้ข้‡่กจไธญ๏ผŒ2่กŒไปฃ็ ไน‹้—ด็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -834,8 +1144,17 @@ object ไปฅ `(` ๅผ€ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ฏญๆณ•ไธŠ่ขซ่งฃๆžไธบไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๆ•ฐ่ฐƒ็”จ */ "newline-call": "Warning", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "Warning", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "Warning", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Warning", /* ้‡ๅคๅฎšไน‰็š„ๅฑ€้ƒจๅ˜้‡ @@ -845,34 +1164,73 @@ object ๅ‡ฝๆ•ฐ่ฐƒ็”จๆ—ถ๏ผŒไผ ๅ…ฅไบ†ๅคšไฝ™็š„ๅ‚ๆ•ฐ */ "redundant-parameter": "Warning", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Hint", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Warning", /* ่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš */ "redundant-value": "Warning", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Warning", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "Information", /* ๅŽ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Hint", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Warning", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Warning", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Warning", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Warning", /* `_ENV` ่ขซ่ฎพ็ฝฎไธบไบ†ๆ–ฐ็š„ๅญ—้ข้‡่กจ๏ผŒไฝ†ๆ˜ฏ่ฏ•ๅ›พ่Žทๅ–็š„ๅ…จๅฑ€ๅ˜้‡ไธๅ†่ฟ™ๅผ ่กจไธญ */ "undefined-env-child": "Information", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Warning", /* ๆœชๅฎšไน‰็š„ๅ…จๅฑ€ๅ˜้‡ */ "undefined-global": "Warning", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Warning", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Warning", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Warning", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Hint", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๆ•ฐ @@ -911,7 +1269,7 @@ Array # diagnostics.workspaceDelay -่ฟ›่กŒๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๅปถ่ฟŸ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ๅฝ“ไฝ ๅฏๅŠจๅทฅไฝœๅŒบ๏ผŒๆˆ–็ผ–่พ‘ไบ†ไปปๆ„ๆ–‡ไปถๅŽ๏ผŒๅฐ†ไผšๅœจๅŽๅฐๅฏนๆ•ดไธชๅทฅไฝœๅŒบ่ฟ›่กŒ้‡ๆ–ฐ่ฏŠๆ–ญใ€‚่ฎพ็ฝฎไธบ่ดŸๆ•ฐๅฏไปฅ็ฆ็”จๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚ +่ฟ›่กŒๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๅปถ่ฟŸ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ ## type @@ -925,6 +1283,28 @@ integer 3000 ``` +# diagnostics.workspaceEvent + +่ฎพ็ฝฎ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๆ—ถๆœบใ€‚ + +## type + +```ts +string +``` + +## enum + +* ``"OnChange"``: ๅฝ“ๆ–‡ไปถๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚ +* ``"OnSave"``: ๅฝ“ๆ–‡ไปถไฟๅญ˜ๆ—ถ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚ +* ``"None"``: ๅ…ณ้—ญๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚ + +## default + +```jsonc +"OnSave" +``` + # diagnostics.workspaceRate ๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„่ฟ่กŒ้€Ÿ็އ๏ผˆ็™พๅˆ†ๆฏ”๏ผ‰ใ€‚้™ไฝŽ่ฏฅๅ€ผไผšๅ‡ๅฐ‘CPUๅ ็”จ๏ผŒไฝ†ๆ˜ฏไนŸไผš้™ไฝŽๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„้€Ÿๅบฆใ€‚ไฝ ๅฝ“ๅ‰ๆญฃๅœจ็ผ–่พ‘็š„ๆ–‡ไปถ็š„่ฏŠๆ–ญๆ€ปๆ˜ฏๅ…จ้€ŸๅฎŒๆˆ๏ผŒไธๅ—่ฏฅ้€‰้กนๅฝฑๅ“ใ€‚ @@ -941,6 +1321,54 @@ integer 100 ``` +# doc.packageName + +ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบpackage๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„ๆ–‡ไปถไธญ่ฎฟ้—ฎใ€‚ + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.privateName + +ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบ็งๆœ‰๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๆ˜ฏ็งๆœ‰ๅญ—ๆฎต๏ผŒๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„็ฑปไธญ่ฎฟ้—ฎใ€‚ + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.protectedName + +ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบๅ—ไฟๆŠค๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๆ˜ฏๅ—ไฟๆŠค็š„ๅญ—ๆฎต๏ผŒๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„็ฑปๆžๅ…ถๅญ็ฑปไธญ่ฎฟ้—ฎใ€‚ + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # format.defaultConfig ้ป˜่ฎค็š„ๆ ผๅผๅŒ–้…็ฝฎ๏ผŒไผ˜ๅ…ˆ็บงไฝŽไบŽๅทฅไฝœๅŒบๅ†…็š„ `.editorconfig` ๆ–‡ไปถใ€‚ @@ -1218,9 +1646,25 @@ integer 1000 ``` +# misc.executablePath + +VSCodeไธญๆŒ‡ๅฎšๅฏๆ‰ง่กŒๆ–‡ไปถ่ทฏๅพ„ใ€‚ + +## type + +```ts +string +``` + +## default + +```jsonc +"" +``` + # misc.parameters -VSCodeไธญๅฏๅŠจ่ฏญ่จ€ๆœๅŠกๆ—ถ็š„[ๅ‘ฝไปค่กŒๅ‚ๆ•ฐ](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)ใ€‚ +VSCodeไธญๅฏๅŠจ่ฏญ่จ€ๆœๅŠกๆ—ถ็š„[ๅ‘ฝไปค่กŒๅ‚ๆ•ฐ](https://luals.github.io/wiki/usage#arguments)ใ€‚ ## type @@ -1234,6 +1678,22 @@ Array [] ``` +# nameStyle.config + +่ฎพๅฎšๅ‘ฝๅ้ฃŽๆ ผๆฃ€ๆŸฅ็š„้…็ฝฎ + +## type + +```ts +Object +``` + +## default + +```jsonc +{} +``` + # runtime.builtin ่ฐƒๆ•ดๅ†…็ฝฎๅบ“็š„ๅฏ็”จ็Šถๆ€๏ผŒไฝ ๅฏไปฅๆ นๆฎๅฎž้™…่ฟ่กŒ็Žฏๅขƒ็ฆ็”จๆމไธๅญ˜ๅœจ็š„ๅบ“๏ผˆๆˆ–้‡ๆ–ฐๅฎšไน‰๏ผ‰ใ€‚ @@ -1268,10 +1728,13 @@ object "ffi": "default", "io": "default", "jit": "default", + "jit.profile": "default", + "jit.util": "default", "math": "default", "os": "default", "package": "default", "string": "default", + "string.buffer": "default", "table": "default", "table.clear": "default", "table.new": "default", @@ -1394,7 +1857,7 @@ false # runtime.plugin -ๆ’ไปถ่ทฏๅพ„๏ผŒ่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/Plugins)ไบ†่งฃ็”จๆณ•ใ€‚ +ๆ’ไปถ่ทฏๅพ„๏ผŒ่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://luals.github.io/wiki/plugins)ไบ†่งฃ็”จๆณ•ใ€‚ ## type @@ -1583,23 +2046,6 @@ Array [] ``` -# telemetry.enable - -ๅฏ็”จ้ฅๆต‹๏ผŒ้€š่ฟ‡็ฝ‘็ปœๅ‘้€ไฝ ็š„็ผ–่พ‘ๅ™จไฟกๆฏไธŽ้”™่ฏฏๆ—ฅๅฟ—ใ€‚ๅœจ[ๆญคๅค„](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้˜…่ฏปๆˆ‘ไปฌ็š„้š็งๅฃฐๆ˜Žใ€‚ - - -## type - -```ts -boolean | null -``` - -## default - -```jsonc -null -``` - # type.castNumberToInteger ๅ…่ฎธๅฐ† `number` ็ฑปๅž‹่ต‹็ป™ `integer` ็ฑปๅž‹ใ€‚ @@ -1654,6 +2100,35 @@ boolean false ``` +# typeFormat.config + +Configures the formatting behavior while typing Lua code. + +## type + +```ts +object +``` + +## default + +```jsonc +{ + /* + Controls if `end` is automatically completed at suitable positions. + */ + "auto_complete_end": "true", + /* + Controls if a separator is automatically appended at the end of a table declaration. + */ + "auto_complete_table_sep": "true", + /* + Controls if a line is formatted at all. + */ + "format_line": "true" +} +``` + # window.progressBar ๅœจ็Šถๆ€ๆ ๆ˜พ็คบ่ฟ›ๅบฆๆกใ€‚ @@ -1701,13 +2176,20 @@ true ## type ```ts -boolean +string ``` +## enum + +* ``"Ask"`` +* ``"Apply"`` +* ``"ApplyInMemory"`` +* ``"Disable"`` + ## default ```jsonc -true +"Ask" ``` # workspace.ignoreDir @@ -1790,22 +2272,6 @@ integer 500 ``` -# workspace.supportScheme - -ไธบไปฅไธ‹ scheme ็š„luaๆ–‡ไปถๆไพ›่ฏญ่จ€ๆœๅŠกใ€‚ - -## type - -```ts -Array -``` - -## default - -```jsonc -["file","untitled","git"] -``` - # workspace.useGitIgnore ๅฟฝ็•ฅ `.gitignore` ไธญๅˆ—ไธพ็š„ๆ–‡ไปถใ€‚ @@ -1824,7 +2290,7 @@ true # workspace.userThirdParty -ๅœจ่ฟ™้‡ŒๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบ“้€‚้…ๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏทๅ‚่€ƒๅ†…็ฝฎ็š„[้…็ฝฎๆ–‡ไปถ่ทฏๅพ„](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd) +ๅœจ่ฟ™้‡ŒๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบ“้€‚้…ๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏทๅ‚่€ƒๅ†…็ฝฎ็š„[้…็ฝฎๆ–‡ไปถ่ทฏๅพ„](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd) ## type diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md index 08fab32b9..8b01d78cc 100644 --- a/doc/zh-tw/config.md +++ b/doc/zh-tw/config.md @@ -1,3 +1,35 @@ +# addonManager.enable + +Whether the addon manager is enabled or not. + +## type + +```ts +boolean +``` + +## default + +```jsonc +true +``` + +# codeLens.enable + +Enable code lens. + +## type + +```ts +boolean +``` + +## default + +```jsonc +false +``` + # completion.autoRequire ่ผธๅ…ฅๅ…งๅฎน็œ‹่ตทไพ†ๆ˜ฏๅ€‹ๆช”ๅๆ™‚๏ผŒ่‡ชๅ‹• `require` ๆญคๆช”ๆกˆใ€‚ @@ -190,6 +222,7 @@ Array * ``"action-after-return"`` * ``"ambiguity-1"`` +* ``"ambiguous-syntax"`` * ``"args-after-dots"`` * ``"assign-type-mismatch"`` * ``"await-in-sync"`` @@ -221,12 +254,41 @@ Array * ``"err-nonstandard-symbol"`` * ``"err-then-as-do"`` * ``"exp-in-action"`` +* ``"global-element"`` * ``"global-in-nil-env"`` +* ``"incomplete-signature-doc"`` * ``"index-in-func-name"`` +* ``"inject-field"`` +* ``"invisible"`` * ``"jump-local-scope"`` * ``"keyword"`` * ``"local-limit"`` * ``"lowercase-global"`` +* ``"lua-doc-miss-sign"`` +* ``"luadoc-error-diag-mode"`` +* ``"luadoc-miss-alias-extends"`` +* ``"luadoc-miss-alias-name"`` +* ``"luadoc-miss-arg-name"`` +* ``"luadoc-miss-cate-name"`` +* ``"luadoc-miss-class-extends-name"`` +* ``"luadoc-miss-class-name"`` +* ``"luadoc-miss-diag-mode"`` +* ``"luadoc-miss-diag-name"`` +* ``"luadoc-miss-field-extends"`` +* ``"luadoc-miss-field-name"`` +* ``"luadoc-miss-fun-after-overload"`` +* ``"luadoc-miss-generic-name"`` +* ``"luadoc-miss-local-name"`` +* ``"luadoc-miss-module-name"`` +* ``"luadoc-miss-operator-name"`` +* ``"luadoc-miss-param-extends"`` +* ``"luadoc-miss-param-name"`` +* ``"luadoc-miss-see-name"`` +* ``"luadoc-miss-sign-name"`` +* ``"luadoc-miss-symbol"`` +* ``"luadoc-miss-type-name"`` +* ``"luadoc-miss-vararg-type"`` +* ``"luadoc-miss-version"`` * ``"malformed-number"`` * ``"miss-end"`` * ``"miss-esc-x"`` @@ -240,10 +302,16 @@ Array * ``"miss-sep-in-table"`` * ``"miss-space-between"`` * ``"miss-symbol"`` +* ``"missing-fields"`` +* ``"missing-global-doc"`` +* ``"missing-local-export-doc"`` * ``"missing-parameter"`` * ``"missing-return"`` * ``"missing-return-value"`` +* ``"name-style-check"`` * ``"need-check-nil"`` +* ``"need-paren"`` +* ``"nesting-long-mark"`` * ``"newfield-call"`` * ``"newline-call"`` * ``"no-unknown"`` @@ -382,10 +450,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -403,6 +476,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -419,6 +495,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -429,6 +506,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -436,6 +514,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -500,10 +579,15 @@ object "await": "Fallback", /* * codestyle-check + * name-style-check * spell-check */ "codestyle": "Fallback", /* + * global-element + */ + "conventions": "Fallback", + /* * duplicate-index * duplicate-set-field */ @@ -521,6 +605,9 @@ object * duplicate-doc-alias * duplicate-doc-field * duplicate-doc-param + * incomplete-signature-doc + * missing-global-doc + * missing-local-export-doc * undefined-doc-class * undefined-doc-name * undefined-doc-param @@ -537,6 +624,7 @@ object * close-non-object * deprecated * discard-returns + * invisible */ "strict": "Fallback", /* @@ -547,6 +635,7 @@ object * assign-type-mismatch * cast-local-type * cast-type-mismatch + * inject-field * need-check-nil * param-type-mismatch * return-type-mismatch @@ -554,6 +643,7 @@ object */ "type-check": "Fallback", /* + * missing-fields * missing-parameter * missing-return * missing-return-value @@ -654,42 +744,128 @@ object ๅ„ชๅ…ˆ้ †ๅบๆญง็พฉ๏ผŒๅฆ‚๏ผš `num or 0 + 1` ๏ผŒๆŽจๆธฌไฝฟ็”จ่€…็š„ๅฏฆ้š›ๆœŸๆœ›็‚บ `(num or 0) + 1` */ "ambiguity-1": "Any", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Opened", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "None", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Opened", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Opened", "circle-doc-class": "Any", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Any", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Opened", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "None", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Any", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Any", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Any", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Any", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Any", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Any", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Any", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Any", /* ๅœจๅญ—้ขๅธธๆ•ธ่กจไธญ้‡่ค‡ๅฎš็พฉไบ†็ดขๅผ• */ "duplicate-index": "Any", - "duplicate-set-field": "Any", + /* + Enable diagnostics for setting the same field in a class more than once. + */ + "duplicate-set-field": "Opened", /* ็ฉบ็จ‹ๅผ็ขผๅ€ๅกŠ */ "empty-block": "Opened", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "None", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅŸŸ่ฎŠๆ•ธ๏ผˆ `_ENV` ่ขซ่จญๅฎš็‚บ `nil`๏ผ‰ */ "global-in-nil-env": "Any", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "None", + "inject-field": "Opened", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Any", + /* ้ฆ–ๅญ—ๆฏๅฐๅฏซ็š„ๅ…จๅŸŸ่ฎŠๆ•ธๅฎš็พฉ */ "lowercase-global": "Any", + "missing-fields": "Any", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "None", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "None", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Any", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Any", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Any", + /* + Enable diagnostics for name style. + */ + "name-style-check": "None", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Opened", /* ๅœจๅญ—้ขๅธธๆ•ธ่กจไธญ๏ผŒ2่กŒ็จ‹ๅผ็ขผไน‹้–“็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ชžๆณ•ไธŠ่ขซ่งฃๆž็‚บไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -699,8 +875,17 @@ object ไปฅ `(` ้–‹ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ชžๆณ•ไธŠ่ขซ่งฃๆž็‚บไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๅผๅ‘ผๅซ */ "newline-call": "Any", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "None", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "None", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Opened", /* ้‡่ค‡ๅฎš็พฉ็š„ๅ€ๅŸŸ่ฎŠๆ•ธ @@ -710,34 +895,73 @@ object ๅ‡ฝๅผๅ‘ผๅซๆ™‚๏ผŒๅ‚ณๅ…ฅไบ†ๅคš้ค˜็š„ๅผ•ๆ•ธ */ "redundant-parameter": "Any", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Opened", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Any", /* ่ณฆๅ€ผๆ“ไฝœๆ™‚๏ผŒๅ€ผ็š„ๆ•ธ้‡ๆฏ”่ขซ่ณฆๅ€ผ็š„ๅฐ่ฑกๅคš */ "redundant-value": "Any", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Opened", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "None", /* ๅพŒ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Opened", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Any", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Any", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Any", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Any", /* `_ENV` ่ขซ่จญๅฎš็‚บไบ†ๆ–ฐ็š„ๅญ—้ขๅธธๆ•ธ่กจ๏ผŒไฝ†ๆ˜ฏ่ฉฆๅœ–็ฒๅ–็š„ๅ…จๅŸŸ่ฎŠๆ•ธไธๅœจ้€™ๅผต่กจไธญ */ "undefined-env-child": "Any", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Opened", /* ๆœชๅฎš็พฉ็š„ๅ…จๅŸŸ่ฎŠๆ•ธ */ "undefined-global": "Any", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Any", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Any", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Any", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Opened", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๅผ @@ -789,42 +1013,128 @@ object ๅ„ชๅ…ˆ้ †ๅบๆญง็พฉ๏ผŒๅฆ‚๏ผš `num or 0 + 1` ๏ผŒๆŽจๆธฌไฝฟ็”จ่€…็š„ๅฏฆ้š›ๆœŸๆœ›็‚บ `(num or 0) + 1` */ "ambiguity-1": "Warning", + /* + Enable diagnostics for assignments in which the value's type does not match the type of the assigned variable. + */ "assign-type-mismatch": "Warning", + /* + Enable diagnostics for calls of asynchronous functions within a synchronous function. + */ "await-in-sync": "Warning", + /* + Enable diagnostics for casts of local variables where the target type does not match the defined type. + */ "cast-local-type": "Warning", + /* + Enable diagnostics for casts where the target type does not match the initial type. + */ "cast-type-mismatch": "Warning", "circle-doc-class": "Warning", + /* + Enable diagnostics for attempts to close a variable with a non-object. + */ "close-non-object": "Warning", + /* + Enable diagnostics for code placed after a break statement in a loop. + */ "code-after-break": "Hint", + /* + Enable diagnostics for incorrectly styled lines. + */ "codestyle-check": "Warning", + /* + Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing. + */ "count-down-loop": "Warning", + /* + Enable diagnostics to highlight deprecated API. + */ "deprecated": "Warning", + /* + Enable diagnostics for files which are required by two different paths. + */ "different-requires": "Warning", + /* + Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored. + */ "discard-returns": "Warning", + /* + Enable diagnostics to highlight a field annotation without a defining class annotation. + */ "doc-field-no-class": "Warning", + /* + Enable diagnostics for a duplicated alias annotation name. + */ "duplicate-doc-alias": "Warning", + /* + Enable diagnostics for a duplicated field annotation name. + */ "duplicate-doc-field": "Warning", + /* + Enable diagnostics for a duplicated param annotation name. + */ "duplicate-doc-param": "Warning", /* ๅœจๅญ—้ขๅธธๆ•ธ่กจไธญ้‡่ค‡ๅฎš็พฉไบ†็ดขๅผ• */ "duplicate-index": "Warning", + /* + Enable diagnostics for setting the same field in a class more than once. + */ "duplicate-set-field": "Warning", /* ็ฉบ็จ‹ๅผ็ขผๅ€ๅกŠ */ "empty-block": "Hint", /* + Enable diagnostics to warn about global elements. + */ + "global-element": "Warning", + /* ไธ่ƒฝไฝฟ็”จๅ…จๅŸŸ่ฎŠๆ•ธ๏ผˆ `_ENV` ่ขซ่จญๅฎš็‚บ `nil`๏ผ‰ */ "global-in-nil-env": "Warning", /* + Incomplete @param or @return annotations for functions. + */ + "incomplete-signature-doc": "Warning", + "inject-field": "Warning", + /* + Enable diagnostics for accesses to fields which are invisible. + */ + "invisible": "Warning", + /* ้ฆ–ๅญ—ๆฏๅฐๅฏซ็š„ๅ…จๅŸŸ่ฎŠๆ•ธๅฎš็พฉ */ "lowercase-global": "Information", + "missing-fields": "Warning", + /* + Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values. + */ + "missing-global-doc": "Warning", + /* + Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values. + */ + "missing-local-export-doc": "Warning", + /* + Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters. + */ "missing-parameter": "Warning", + /* + Enable diagnostics for functions with return annotations which have no return statement. + */ "missing-return": "Warning", + /* + Enable diagnostics for return statements without values although the containing function declares returns. + */ "missing-return-value": "Warning", + /* + Enable diagnostics for name style. + */ + "name-style-check": "Warning", + /* + Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before. + */ "need-check-nil": "Warning", /* ๅœจๅญ—้ขๅธธๆ•ธ่กจไธญ๏ผŒ2่กŒ็จ‹ๅผ็ขผไน‹้–“็ผบๅฐ‘ๅˆ†้š”็ฌฆ๏ผŒๅœจ่ชžๆณ•ไธŠ่ขซ่งฃๆž็‚บไบ†ไธ€ๆฌก็ดขๅผ•ๆ“ไฝœ @@ -834,8 +1144,17 @@ object ไปฅ `(` ้–‹ๅง‹็š„ๆ–ฐ่กŒ๏ผŒๅœจ่ชžๆณ•ไธŠ่ขซ่งฃๆž็‚บไบ†ไธŠไธ€่กŒ็š„ๅ‡ฝๅผๅ‘ผๅซ */ "newline-call": "Warning", + /* + Enable diagnostics for cases in which the type cannot be inferred. + */ "no-unknown": "Warning", + /* + Enable diagnostics for calls to `coroutine.yield()` when it is not permitted. + */ "not-yieldable": "Warning", + /* + Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition. + */ "param-type-mismatch": "Warning", /* ้‡่ค‡ๅฎš็พฉ็š„ๅ€ๅŸŸ่ฎŠๆ•ธ @@ -845,34 +1164,73 @@ object ๅ‡ฝๅผๅ‘ผๅซๆ™‚๏ผŒๅ‚ณๅ…ฅไบ†ๅคš้ค˜็š„ๅผ•ๆ•ธ */ "redundant-parameter": "Warning", + /* + Enable diagnostics for return statements which are not needed because the function would exit on its own. + */ "redundant-return": "Hint", + /* + Enable diagnostics for return statements which return an extra value which is not specified by a return annotation. + */ "redundant-return-value": "Warning", /* ่ณฆๅ€ผๆ“ไฝœๆ™‚๏ผŒๅ€ผ็š„ๆ•ธ้‡ๆฏ”่ขซ่ณฆๅ€ผ็š„ๅฐ่ฑกๅคš */ "redundant-value": "Warning", + /* + Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation. + */ "return-type-mismatch": "Warning", + /* + Enable diagnostics for typos in strings. + */ "spell-check": "Information", /* ๅพŒ็ฝฎ็ฉบๆ ผ */ "trailing-space": "Hint", + /* + Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`). + */ "unbalanced-assignments": "Warning", + /* + Enable diagnostics for class annotations in which an undefined class is referenced. + */ "undefined-doc-class": "Warning", + /* + Enable diagnostics for type annotations referencing an undefined type or alias. + */ "undefined-doc-name": "Warning", + /* + Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition. + */ "undefined-doc-param": "Warning", /* `_ENV` ่ขซ่จญๅฎš็‚บไบ†ๆ–ฐ็š„ๅญ—้ขๅธธๆ•ธ่กจ๏ผŒไฝ†ๆ˜ฏ่ฉฆๅœ–็ฒๅ–็š„ๅ…จๅŸŸ่ฎŠๆ•ธไธๅœจ้€™ๅผต่กจไธญ */ "undefined-env-child": "Information", + /* + Enable diagnostics for cases in which an undefined field of a variable is read. + */ "undefined-field": "Warning", /* ๆœชๅฎš็พฉ็š„ๅ…จๅŸŸ่ฎŠๆ•ธ */ "undefined-global": "Warning", + /* + Enable diagnostics for casts of undefined variables. + */ "unknown-cast-variable": "Warning", + /* + Enable diagnostics in cases in which an unknown diagnostics code is entered. + */ "unknown-diag-code": "Warning", + /* + Enable diagnostics for unknown operators. + */ "unknown-operator": "Warning", + /* + Enable diagnostics for unreachable code. + */ "unreachable-code": "Hint", /* ๆœชไฝฟ็”จ็š„ๅ‡ฝๅผ @@ -911,7 +1269,7 @@ Array # diagnostics.workspaceDelay -้€ฒ่กŒๅทฅไฝœๅ€่จบๆ–ท็š„ๅปถ้ฒ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚็•ถไฝ ๅ•Ÿๅ‹•ๅทฅไฝœๅ€๏ผŒๆˆ–็ทจ่ผฏไบ†ไปปไฝ•ๆช”ๆกˆๅพŒ๏ผŒๅฐ‡ๆœƒๅœจ่ƒŒๆ™ฏๅฐๆ•ดๅ€‹ๅทฅไฝœๅ€้€ฒ่กŒ้‡ๆ–ฐ่จบๆ–ทใ€‚่จญๅฎš็‚บ่ฒ ๆ•ธๅฏไปฅๅœ็”จๅทฅไฝœๅ€่จบๆ–ทใ€‚ +้€ฒ่กŒๅทฅไฝœๅ€่จบๆ–ท็š„ๅปถ้ฒ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ ## type @@ -925,6 +1283,28 @@ integer 3000 ``` +# diagnostics.workspaceEvent + +Set the time to trigger workspace diagnostics. + +## type + +```ts +string +``` + +## enum + +* ``"OnChange"``: Trigger workspace diagnostics when the file is changed. +* ``"OnSave"``: Trigger workspace diagnostics when the file is saved. +* ``"None"``: Disable workspace diagnostics. + +## default + +```jsonc +"OnSave" +``` + # diagnostics.workspaceRate ๅทฅไฝœๅ€่จบๆ–ท็š„ๅŸท่กŒ้€Ÿ็އ๏ผˆ็™พๅˆ†ๆฏ”๏ผ‰ใ€‚้™ไฝŽ่ฉฒๅ€ผๆœƒๆธ›ๅฐ‘CPUไฝฟ็”จ็އ๏ผŒไฝ†ๆ˜ฏไนŸๆœƒ้™ไฝŽๅทฅไฝœๅ€่จบๆ–ท็š„้€Ÿๅบฆใ€‚ไฝ ็›ฎๅ‰ๆญฃๅœจ็ทจ่ผฏ็š„ๆช”ๆกˆ็š„่จบๆ–ท็ธฝๆ˜ฏๅ…จ้€ŸๅฎŒๆˆ๏ผŒไธๅ—่ฉฒ้ธ้ …ๅฝฑ้Ÿฟใ€‚ @@ -941,6 +1321,54 @@ integer 100 ``` +# doc.packageName + +Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.privateName + +Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + +# doc.protectedName + +Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # format.defaultConfig ้ ่จญ็š„ๆ ผๅผๅŒ–็ต„ๆ…‹๏ผŒๅ„ชๅ…ˆ้ †ๅบไฝŽๆ–ผๅทฅไฝœๅ€ๅ…ง็š„ `.editorconfig` ๆช”ๆกˆใ€‚ @@ -1218,9 +1646,25 @@ integer 1000 ``` +# misc.executablePath + +Specify the executable path in VSCode. + +## type + +```ts +string +``` + +## default + +```jsonc +"" +``` + # misc.parameters -VSCodeไธญๅ•Ÿๅ‹•่ชž่จ€ไผบๆœๆ™‚็š„[ๅ‘ฝไปคๅˆ—ๅƒๆ•ธ](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)ใ€‚ +VSCodeไธญๅ•Ÿๅ‹•่ชž่จ€ไผบๆœๆ™‚็š„[ๅ‘ฝไปคๅˆ—ๅƒๆ•ธ](https://luals.github.io/wiki/usage#arguments)ใ€‚ ## type @@ -1234,6 +1678,22 @@ Array [] ``` +# nameStyle.config + +Set name style config + +## type + +```ts +Object +``` + +## default + +```jsonc +{} +``` + # runtime.builtin ่ชฟๆ•ดๅ…งๅปบๅบซ็š„ๅ•Ÿ็”จ็‹€ๆ…‹๏ผŒไฝ ๅฏไปฅๆ นๆ“šๅฏฆ้š›ๅŸท่กŒ็’ฐๅขƒๅœ็”จ๏ผˆๆˆ–้‡ๆ–ฐๅฎš็พฉ๏ผ‰ไธๅญ˜ๅœจ็š„ๅบซใ€‚ @@ -1268,10 +1728,13 @@ object "ffi": "default", "io": "default", "jit": "default", + "jit.profile": "default", + "jit.util": "default", "math": "default", "os": "default", "package": "default", "string": "default", + "string.buffer": "default", "table": "default", "table.clear": "default", "table.new": "default", @@ -1394,7 +1857,7 @@ false # runtime.plugin -ๅปถไผธๆจก็ต„่ทฏๅพ‘๏ผŒ่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Plugins)็žญ่งฃ็”จๆณ•ใ€‚ +ๅปถไผธๆจก็ต„่ทฏๅพ‘๏ผŒ่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://luals.github.io/wiki/plugins)็žญ่งฃ็”จๆณ•ใ€‚ ## type @@ -1583,23 +2046,6 @@ Array [] ``` -# telemetry.enable - -ๅ•Ÿ็”จ้™ๆธฌ๏ผŒ้€้Ž็ถฒ่ทฏ็™ผ้€ไฝ ็š„็ทจ่ผฏๅ™จ่ณ‡่จŠ่ˆ‡้Œฏ่ชคๆ—ฅ่ชŒใ€‚ๅœจ[ๆญค่™•](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้–ฑ่ฎ€ๆˆ‘ๅ€‘็š„้šฑ็ง่ฒๆ˜Žใ€‚ - - -## type - -```ts -boolean | null -``` - -## default - -```jsonc -null -``` - # type.castNumberToInteger ๅ…่จฑๅฐ‡ `number` ้กžๅž‹่ณฆๅ€ผ็ตฆ `integer` ้กžๅž‹ใ€‚ @@ -1654,6 +2100,35 @@ boolean false ``` +# typeFormat.config + +Configures the formatting behavior while typing Lua code. + +## type + +```ts +object +``` + +## default + +```jsonc +{ + /* + Controls if `end` is automatically completed at suitable positions. + */ + "auto_complete_end": "true", + /* + Controls if a separator is automatically appended at the end of a table declaration. + */ + "auto_complete_table_sep": "true", + /* + Controls if a line is formatted at all. + */ + "format_line": "true" +} +``` + # window.progressBar ๅœจ็‹€ๆ…‹ๆฌ„้กฏ็คบ้€ฒๅบฆๆขใ€‚ @@ -1701,13 +2176,20 @@ true ## type ```ts -boolean +string ``` +## enum + +* ``"Ask"`` +* ``"Apply"`` +* ``"ApplyInMemory"`` +* ``"Disable"`` + ## default ```jsonc -true +"Ask" ``` # workspace.ignoreDir @@ -1790,22 +2272,6 @@ integer 500 ``` -# workspace.supportScheme - -็‚บไปฅไธ‹ `scheme` ็š„luaๆช”ๆกˆๆไพ›่ชž่จ€ไผบๆœใ€‚ - -## type - -```ts -Array -``` - -## default - -```jsonc -["file","untitled","git"] -``` - # workspace.useGitIgnore ๅฟฝ็•ฅ `.gitignore` ไธญๅˆ—่ˆ‰็š„ๆช”ๆกˆใ€‚ @@ -1824,7 +2290,7 @@ true # workspace.userThirdParty -ๅœจ้€™่ฃกๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบซ้ฉๆ‡‰ๆช”ๆกˆ่ทฏๅพ‘๏ผŒ่ซ‹ๅƒ่€ƒๅ…งๅปบ็š„[็ต„ๆ…‹ๆช”ๆกˆ่ทฏๅพ‘](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd) +ๅœจ้€™่ฃกๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบซ้ฉๆ‡‰ๆช”ๆกˆ่ทฏๅพ‘๏ผŒ่ซ‹ๅƒ่€ƒๅ…งๅปบ็š„[็ต„ๆ…‹ๆช”ๆกˆ่ทฏๅพ‘](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd) ## type diff --git a/locale/en-us/meta.lua b/locale/en-us/meta.lua index 9ab6aab88..e1ca55773 100644 --- a/locale/en-us/meta.lua +++ b/locale/en-us/meta.lua @@ -519,11 +519,11 @@ math.log10 = 'Returns the base-10 logarithm of x.' math.max = 'Returns the argument with the maximum value, according to the Lua operator `<`.' -math.maxinteger = +math.maxinteger['>5.3'] = 'An integer with the maximum value for an integer.' math.min = 'Returns the argument with the minimum value, according to the Lua operator `<`.' -math.mininteger = +math.mininteger['>5.3'] = 'An integer with the minimum value for an integer.' math.modf = 'Returns the integral part of `x` and the fractional part of `x`.' @@ -557,11 +557,11 @@ math.tan = 'Returns the tangent of `x` (assumed to be in radians).' math.tanh = 'Returns the hyperbolic tangent of `x` (assumed to be in radians).' -math.tointeger = +math.tointeger['>5.3'] = 'If the value `x` is convertible to an integer, returns that integer.' -math.type = +math.type['>5.3'] = 'Returns `"integer"` if `x` is an integer, `"float"` if it is a float, or `nil` if `x` is not a number.' -math.ult = +math.ult['>5.3'] = 'Returns `true` if and only if `m` is below `n` when they are compared as unsigned integers.' os = diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 05a5c0d1f..20d858bb1 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -1,7 +1,7 @@ DIAG_LINE_ONLY_SPACE = 'Line with spaces only.' DIAG_LINE_POST_SPACE = -'Line with postspace.' +'Line with trailing space.' DIAG_UNUSED_LOCAL = 'Unused local `{}`.' DIAG_UNDEF_GLOBAL = @@ -114,6 +114,22 @@ DIAG_UNDEFINED_DOC_NAME = 'Undefined type or alias `{}`.' DIAG_UNDEFINED_DOC_PARAM = 'Undefined param `{}`.' +DIAG_MISSING_GLOBAL_DOC_COMMENT = +'Missing comment for global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_PARAM = +'Missing @param annotation for parameter `{}` in global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_RETURN = +'Missing @return annotation at index `{}` in global function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT = +'Missing comment for exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM = +'Missing @param annotation for parameter `{}` in exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN = +'Missing @return annotation at index `{}` in exported local function `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM = +'Incomplete signature. Missing @param annotation for parameter `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN = +'Incomplete signature. Missing @return annotation at index `{}`.' DIAG_UNKNOWN_DIAG_CODE = 'Unknown diagnostic code `{}`.' DIAG_CAST_LOCAL_TYPE = @@ -129,21 +145,37 @@ DIAG_UNKNOWN_CAST_VARIABLE = DIAG_CAST_TYPE_MISMATCH = 'Cannot convert `{def}` to `{ref}`ใ€‚' DIAG_MISSING_RETURN_VALUE = -'At least {min} return values are required, but here only {rmax} values are returned.' +'Annotations specify that at least {min} return value(s) are required, found {rmax} returned here instead.' DIAG_MISSING_RETURN_VALUE_RANGE = -'At least {min} return values are required, but here only {rmin} to {rmax} values are returned.' +'Annotations specify that at least {min} return value(s) are required, found {rmin} to {rmax} returned here instead.' DIAG_REDUNDANT_RETURN_VALUE = -'At most {max} values returned, but the {rmax}th value was returned here.' +'Annotations specify that at most {max} return value(s) are required, found {rmax} returned here instead.' DIAG_REDUNDANT_RETURN_VALUE_RANGE = -'At most {max} values returned, but {rmin}th to {rmax}th values were returned here.' +'Annotations specify that at most {max} return value(s) are required, found {rmin} to {rmax} returned here instead.' DIAG_MISSING_RETURN = -'Return value is required here.' +'Annotations specify that a return value is required here.' DIAG_RETURN_TYPE_MISMATCH = -'The type of the {index} return value is `{def}`, but the actual return is `{ref}`.' +'Annotations specify that return value #{index} has a type of `{def}`, returning value of type `{ref}` here instead.' DIAG_UNKNOWN_OPERATOR = 'Unknown operator `{}`.' DIAG_UNREACHABLE_CODE = 'Unreachable code.' +DIAG_INVISIBLE_PRIVATE = +'Field `{field}` is private, it can only be accessed in class `{class}`.' +DIAG_INVISIBLE_PROTECTED = +'Field `{field}` is protected, it can only be accessed in class `{class}` and its subclasses.' +DIAG_INVISIBLE_PACKAGE = +'Field `{field}` can only be accessed in same file `{uri}`.' +DIAG_GLOBAL_ELEMENT = +'Element is global.' +DIAG_MISSING_FIELDS = +'Missing required fields in type `{1}`: {2}' +DIAG_INJECT_FIELD = +'Fields cannot be injected into the reference of `{class}` for `{field}`. {fix}' +DIAG_INJECT_FIELD_FIX_CLASS = +'To do so, use `---@class` for `{node}`.' +DIAG_INJECT_FIELD_FIX_TABLE = +'To allow injection, add `{fix}` to the definition.' MWS_NOT_SUPPORT = '{} does not support multi workspace for now, I may need to restart to support the new workspace ...' @@ -171,9 +203,9 @@ WORKSPACE_DIAGNOSTIC = WORKSPACE_SKIP_HUGE_FILE = 'For performance reasons, the parsing of this file has been stopped: {}' WORKSPACE_NOT_ALLOWED = -'Your workspace is set to `{}`. Lua language server refused to load this directory. Please check your configuration.[learn more here](https://github.com/sumneko/lua-language-server/wiki/FAQ#why-is-the-server-scanning-the-wrong-folder)' +'Your workspace is set to `{}`. Lua language server refused to load this directory. Please check your configuration.[learn more here](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder)' WORKSPACE_SCAN_TOO_MUCH = -'More than {} files have been scanned. The current scanned directory is `{}`. Please confirm whether the configuration is correct.' +'More than {} files have been scanned. The current scanned directory is `{}`. Please see the [FAQ](https://luals.github.io/wiki/faq/#how-can-i-improve-startup-speeds) to see how you can include fewer files. It is also possible that your [configuration is incorrect](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder).' PARSER_CRASH = 'Parser crashed! Last words:{}' @@ -269,6 +301,14 @@ PARSER_INDEX_IN_FUNC_NAME = 'The `[name]` form cannot be used in the name of a named function.' PARSER_UNKNOWN_ATTRIBUTE = 'Local attribute should be `const` or `close`' +PARSER_AMBIGUOUS_SYNTAX = +'In Lua 5.1, the left brackets called by the function must be in the same line as the function.' +PARSER_NEED_PAREN = +'Need to add a pair of parentheses.' +PARSER_NESTING_LONG_MARK = +'Nesting of `[[...]]` is not allowed in Lua 5.1 .' +PARSER_LOCAL_LIMIT = +'Only 200 active local variables and upvalues can be existed at the same time.' PARSER_LUADOC_MISS_CLASS_NAME = ' expected.' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = @@ -414,6 +454,10 @@ ACTION_MARK_ASYNC = 'Mark current function as async.' ACTION_ADD_DICT = 'Add \'{}\' to workspace dict' +ACTION_FIX_ADD_PAREN = +'Add parentheses.' +ACTION_AUTOREQUIRE = +"Import '{}' as {}" COMMAND_DISABLE_DIAG = 'Disable diagnostics' @@ -435,6 +479,8 @@ COMMAND_JSON_TO_LUA_FAILED = 'Convert JSON to Lua failed: {}' COMMAND_ADD_DICT = 'Add Word to dictionary' +COMMAND_REFERENCE_COUNT = +'{} references' COMPLETION_IMPORT_FROM = 'Import from {}' @@ -512,7 +558,7 @@ WINDOW_APPLY_SETTING = WINDOW_CHECK_SEMANTIC = 'If you are using the color theme in the market, you may need to modify `editor.semanticHighlighting.enabled` to `true` to make semantic tokens take effect.' WINDOW_TELEMETRY_HINT = -'Please allow sending anonymous usage data and error reports to help us further improve this extension. Read our privacy policy [here](https://github.com/sumneko/lua-language-server/wiki/Home#privacy) .' +'Please allow sending anonymous usage data and error reports to help us further improve this extension. Read our privacy policy [here](https://luals.github.io/privacy#language-server) .' WINDOW_TELEMETRY_ENABLE = 'Allow' WINDOW_TELEMETRY_DISABLE = @@ -535,6 +581,14 @@ WINDOW_ASK_APPLY_LIBRARY = 'Do you need to configure your work environment as `{}`?' WINDOW_SEARCHING_IN_FILES = 'Searching in files...' +WINDOW_CONFIG_LUA_DEPRECATED = +'`config.lua` is deprecated, please use `config.json` instead.' +WINDOW_CONVERT_CONFIG_LUA = +'Convert to `config.json`' +WINDOW_MODIFY_REQUIRE_PATH = +'Do you want to modify the require path?' +WINDOW_MODIFY_REQUIRE_OK = +'Modify' CONFIG_LOAD_FAILED = 'Unable to read the settings file: {}' @@ -542,6 +596,24 @@ CONFIG_LOAD_ERROR = 'Setting file loading error: {}' CONFIG_TYPE_ERROR = 'The setting file must be in lua or json format: {}' +CONFIG_MODIFY_FAIL_SYNTAX_ERROR = +'Failed to modify settings, there are syntax errors in the settings file: {}' +CONFIG_MODIFY_FAIL_NO_WORKSPACE = +[[ +Failed to modify settings: +* The current mode is single-file mode, server cannot create `.luarc.json` without workspace. +* The language client dose not support modifying settings from the server side. + +Please modify following settings manually: +{} +]] +CONFIG_MODIFY_FAIL = +[[ +Failed to modify settings + +Please modify following settings manually: +{} +]] PLUGIN_RUNTIME_ERROR = [[ @@ -576,6 +648,49 @@ CLI_CHECK_SUCCESS = 'Diagnosis completed, no problems found' CLI_CHECK_RESULTS = 'Diagnosis complete, {} problems found, see {}' +CLI_DOC_INITING = +'Loading documents ...' +CLI_DOC_DONE = +[[ +Document exporting completed! +Raw data: {} +Markdown(example): {} +]] + +TYPE_ERROR_ENUM_GLOBAL_DISMATCH = +'Type `{child}` cannot match enumeration type of `{parent}`' +TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED = +'Cannot use generic `{child}` in enumeration' +TYPE_ERROR_ENUM_LITERAL_DISMATCH = +'Literal `{child}` cannot match the enumeration value of `{parent}`' +TYPE_ERROR_ENUM_OBJECT_DISMATCH = +'The object `{child}` cannot match the enumeration value of `{parent}`. They must be the same object' +TYPE_ERROR_ENUM_NO_OBJECT = +'The passed in enumeration value `{child}` is not recognized' +TYPE_ERROR_INTEGER_DISMATCH = +'Literal `{child}` cannot match integer `{parent}`' +TYPE_ERROR_STRING_DISMATCH = +'Literal `{child}` cannot match string `{parent}`' +TYPE_ERROR_BOOLEAN_DISMATCH = +'Literal `{child}` cannot match boolean `{parent}`' +TYPE_ERROR_TABLE_NO_FIELD = +'Field `{key}` does not exist in the table' +TYPE_ERROR_TABLE_FIELD_DISMATCH = +'The type of field `{key}` is `{child}`, which cannot match `{parent}`' +TYPE_ERROR_CHILD_ALL_DISMATCH = +'All subtypes in `{child}` cannot match `{parent}`' +TYPE_ERROR_PARENT_ALL_DISMATCH = +'`{child}` cannot match any subtypes in `{parent}`' +TYPE_ERROR_UNION_DISMATCH = +'`{child}` cannot match `{parent}`' +TYPE_ERROR_OPTIONAL_DISMATCH = +'Optional type cannot match `{parent}`' +TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = +'The number `{child}` cannot be converted to an integer' +TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = +'Cannot convert number type to integer type' +TYPE_ERROR_DISMATCH = +'Type `{child}` cannot match `{parent}`' LUADOC_DESC_CLASS = [=[ @@ -588,7 +703,7 @@ Defines a class/table structure Manager = {} ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#class) +[View Wiki](https://luals.github.io/wiki/annotations#class) ]=] LUADOC_DESC_TYPE = [=[ @@ -639,7 +754,7 @@ local x --x[""] is true local myFunction ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#types-and-type) +[View Wiki](https://luals.github.io/wiki/annotations#type) ]=] LUADOC_DESC_ALIAS = [=[ @@ -674,8 +789,22 @@ function find(path, pattern) end ---@param style font-style Style to apply function setFontStyle(style) end ``` + +### Literal Enum +``` +local enums = { + READ = 0, + WRITE = 1, + CLOSED = 2 +} + +---@alias FileStates +---| `enums.READ` +---| `enums.WRITE` +---| `enums.CLOSE` +``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +[View Wiki](https://luals.github.io/wiki/annotations#alias) ]=] LUADOC_DESC_PARAM = [=[ @@ -700,7 +829,7 @@ function get(url, headers, timeout) end function concat(base, ...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#param) +[View Wiki](https://luals.github.io/wiki/annotations#param) ]=] LUADOC_DESC_RETURN = [=[ @@ -738,15 +867,16 @@ function getFirstLast() end function getTags(item) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#return) +[View Wiki](https://luals.github.io/wiki/annotations#return) ]=] LUADOC_DESC_FIELD = [=[ Declare a field in a class/table. This allows you to provide more in-depth -documentation for a table. +documentation for a table. As of `v3.6.0`, you can mark a field as `private`, +`protected`, `public`, or `package`. ## Syntax -`---@field [description]` +`---@field [scope] [description]` ## Usage ``` @@ -768,7 +898,7 @@ response = get("localhost") statusCode = response.status.code ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#field) +[View Wiki](https://luals.github.io/wiki/annotations#field) ]=] LUADOC_DESC_GENERIC = [=[ @@ -825,7 +955,7 @@ local v = Generic("Foo") -- v is an object of Foo -- we give for key (K) or value (V) ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#generics-and-generic) +[View Wiki](https://luals.github.io/wiki/annotations/#generic) ]=] LUADOC_DESC_VARARG = [=[ @@ -844,7 +974,7 @@ provide typing or allow descriptions. function concat(...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#vararg) +[View Wiki](https://luals.github.io/wiki/annotations/#vararg) ]=] LUADOC_DESC_OVERLOAD = [=[ @@ -859,7 +989,7 @@ Allows defining of multiple function signatures. function table.insert(t, position, value) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#overload) +[View Wiki](https://luals.github.io/wiki/annotations#overload) ]=] LUADOC_DESC_DEPRECATED = [=[ @@ -870,7 +1000,7 @@ being ~~struck through~~. `---@deprecated` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#deprecated) +[View Wiki](https://luals.github.io/wiki/annotations#deprecated) ]=] LUADOC_DESC_META = [=[ @@ -885,7 +1015,7 @@ There are 3 main distinctions to note with meta files: `---@meta` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#meta) +[View Wiki](https://luals.github.io/wiki/annotations#meta) ]=] LUADOC_DESC_VERSION = [=[ @@ -910,7 +1040,7 @@ function onlyWorksInJIT() end function oldLuaOnly() end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#version) +[View Wiki](https://luals.github.io/wiki/annotations#version) ]=] LUADOC_DESC_SEE = [=[ @@ -920,7 +1050,7 @@ Define something that can be viewed for more information `---@see ` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#see) +[View Wiki](https://luals.github.io/wiki/annotations#see) ]=] LUADOC_DESC_DIAGNOSTIC = [=[ @@ -928,7 +1058,7 @@ Enable/disable diagnostics for error/warnings/etc. Actions: `disable`, `enable`, `disable-line`, `disable-next-line` -[Names](https://github.com/sumneko/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) +[Names](https://github.com/LuaLS/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) ## Syntax `---@diagnostic [: ]` @@ -946,7 +1076,7 @@ local unused = "hello world" ---@diagnostic enable: unused-local ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#diagnostic) +[View Wiki](https://luals.github.io/wiki/annotations#diagnostic) ]=] LUADOC_DESC_MODULE = [=[ @@ -963,7 +1093,7 @@ local stringUtils local module = require('string.utils') ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#module) +[View Wiki](https://luals.github.io/wiki/annotations#module) ]=] LUADOC_DESC_ASYNC = [=[ @@ -973,7 +1103,7 @@ Marks a function as asynchronous. `---@async` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#async) +[View Wiki](https://luals.github.io/wiki/annotations#async) ]=] LUADOC_DESC_NODISCARD = [=[ @@ -985,7 +1115,7 @@ be ignored. `---@nodiscard` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#nodiscard) +[View Wiki](https://luals.github.io/wiki/annotations#nodiscard) ]=] LUADOC_DESC_CAST = [=[ @@ -1020,7 +1150,7 @@ local x --> string|table print(x) --> table ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#cast) +[View Wiki](https://luals.github.io/wiki/annotations#cast) ]=] LUADOC_DESC_OPERATOR = [=[ @@ -1050,12 +1180,12 @@ pA = Passcode.new(1234) pB = -pA --> integer ``` -[View Request](https://github.com/sumneko/lua-language-server/issues/599) +[View Request](https://github.com/LuaLS/lua-language-server/issues/599) ]=] LUADOC_DESC_ENUM = [=[ Mark a table as an enum. If you want an enum but can't define it as a Lua -table, take a look at the [`@alias`](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +table, take a look at the [`@alias`](https://luals.github.io/wiki/annotations#alias) tag. ## Syntax @@ -1079,3 +1209,103 @@ local function setColor(color) end setColor(colors.green) ``` ]=] +LUADOC_DESC_SOURCE = +[=[ +Provide a reference to some source code which lives in another file. When +searching for the definition of an item, its `@source` will be used. + +## Syntax +`@source ` + +## Usage +``` +---You can use absolute paths +---@source C:/Users/me/Documents/program/myFile.c +local a + +---Or URIs +---@source file:///C:/Users/me/Documents/program/myFile.c:10 +local b + +---Or relative paths +---@source local/file.c +local c + +---You can also include line and char numbers +---@source local/file.c:10:8 +local d +``` +]=] +LUADOC_DESC_PACKAGE = +[=[ +Mark a function as private to the file it is defined in. A packaged function +cannot be accessed from another file. + +## Syntax +`@package` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@package +---This cannot be accessed in another file +function Animal:eyesCount() + return self.eyes +end +``` +]=] +LUADOC_DESC_PRIVATE = +[=[ +Mark a function as private to a @class. Private functions can be accessed only +from within their class and are not accessible from child classes. + +## Syntax +`@private` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@private +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---NOT PERMITTED! +myDog:eyesCount(); +``` +]=] +LUADOC_DESC_PROTECTED = +[=[ +Mark a function as protected within a @class. Protected functions can be +accessed only from within their class or from child classes. + +## Syntax +`@protected` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@protected +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---Permitted because function is protected, not private. +myDog:eyesCount(); +``` +]=] diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua index 112640c02..9ef46b869 100644 --- a/locale/en-us/setting.lua +++ b/locale/en-us/setting.lua @@ -1,5 +1,7 @@ ---@diagnostic disable: undefined-global +config.addonManager.enable = +"Whether the addon manager is enabled or not." config.runtime.version = "Lua runtime version." config.runtime.path = @@ -25,7 +27,7 @@ config.runtime.unicodeName = config.runtime.nonstandardSymbol = "Supports non-standard symbols. Make sure that your runtime environment supports these symbols." config.runtime.plugin = -"Plugin path. Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/Plugins) to learn more." +"Plugin path. Please read [wiki](https://luals.github.io/wiki/plugins) to learn more." config.runtime.pluginArgs = "Additional arguments for the plugin." config.runtime.fileEncoding = @@ -77,8 +79,16 @@ Modify the diagnostic needed file status in a group. `Fallback` means that diagnostics in this group are controlled by `diagnostics.neededFileStatus` separately. Other settings will override individual settings without end of `!`. ]] +config.diagnostics.workspaceEvent = +"Set the time to trigger workspace diagnostics." +config.diagnostics.workspaceEvent.OnChange = +"Trigger workspace diagnostics when the file is changed." +config.diagnostics.workspaceEvent.OnSave = +"Trigger workspace diagnostics when the file is saved." +config.diagnostics.workspaceEvent.None = +"Disable workspace diagnostics." config.diagnostics.workspaceDelay = -"Latency (milliseconds) for workspace diagnostics. When you start the workspace, or edit any file, the entire workspace will be re-diagnosed in the background. Set to negative to disable workspace diagnostics." +"Latency (milliseconds) for workspace diagnostics." config.diagnostics.workspaceRate = "Workspace diagnostics run rate (%). Decreasing this value reduces CPU usage, but also reduces the speed of workspace diagnostics. The diagnosis of the file you are currently editing is always done at full speed and is not affected by this setting." config.diagnostics.libraryFiles = @@ -125,7 +135,7 @@ Automatic detection and adaptation of third-party libraries, currently supported * Jass ]] config.workspace.userThirdParty = -'Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd)' +'Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd)' config.workspace.supportScheme = 'Provide language server for the Lua files of the following scheme.' config.completion.enable = @@ -248,6 +258,8 @@ config.hint.semicolon.SameLine = 'When two statements are on the same line, display a semicolon between them.' config.hint.semicolon.Disable = 'Disable virtual semicolons.' +config.codeLens.enable = +'Enable code lens.' config.format.enable = 'Enable code formatter.' config.format.defaultConfig = @@ -257,20 +269,16 @@ Read [formatter docs](https://github.com/CppCXY/EmmyLuaCodeStyle/tree/master/doc ]] config.spell.dict = 'Custom words for spell checking.' +config.nameStyle.config = +'Set name style config' config.telemetry.enable = [[ -Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://github.com/sumneko/lua-language-server/wiki/Home#privacy). +Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://luals.github.io/privacy/#language-server). ]] config.misc.parameters = -'[Command line parameters](https://github.com/sumneko/lua-telemetry-server/tree/master/method) when starting the language service in VSCode.' -config.IntelliSense.traceLocalSet = -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceReturn = -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceBeSetted = -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceFieldInject = -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' +'[Command line parameters](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) when starting the language server in VSCode.' +config.misc.executablePath = +'Specify the executable path in VSCode.' config.type.castNumberToInteger = 'Allowed to assign the `number` type to the `integer` type.' config.type.weakUnionCheck = @@ -285,6 +293,18 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = +[[ +When a parameter type is not annotated, it is inferred from the function's call sites. + +When this setting is `false`, the type of the parameter is `any` when it is not annotated. +]] +config.doc.privateName = +'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' +config.doc.protectedName = +'Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses.' +config.doc.packageName = +'Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located.' config.diagnostics['unused-local'] = 'Enable unused local variable diagnostics.' config.diagnostics['unused-function'] = @@ -319,3 +339,104 @@ config.diagnostics['empty-block'] = 'Enable empty code block diagnostics.' config.diagnostics['redundant-value'] = 'Enable the redundant values assigned diagnostics. It\'s raised during assignment operation, when the number of values is higher than the number of objects being assigned.' +config.diagnostics['assign-type-mismatch'] = +'Enable diagnostics for assignments in which the value\'s type does not match the type of the assigned variable.' +config.diagnostics['await-in-sync'] = +'Enable diagnostics for calls of asynchronous functions within a synchronous function.' +config.diagnostics['cast-local-type'] = +'Enable diagnostics for casts of local variables where the target type does not match the defined type.' +config.diagnostics['cast-type-mismatch'] = +'Enable diagnostics for casts where the target type does not match the initial type.' +config.diagnostics['circular-doc-class'] = +'Enable diagnostics for two classes inheriting from each other introducing a circular relation.' +config.diagnostics['close-non-object'] = +'Enable diagnostics for attempts to close a variable with a non-object.' +config.diagnostics['code-after-break'] = +'Enable diagnostics for code placed after a break statement in a loop.' +config.diagnostics['codestyle-check'] = +'Enable diagnostics for incorrectly styled lines.' +config.diagnostics['count-down-loop'] = +'Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing.' +config.diagnostics['deprecated'] = +'Enable diagnostics to highlight deprecated API.' +config.diagnostics['different-requires'] = +'Enable diagnostics for files which are required by two different paths.' +config.diagnostics['discard-returns'] = +'Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored.' +config.diagnostics['doc-field-no-class'] = +'Enable diagnostics to highlight a field annotation without a defining class annotation.' +config.diagnostics['duplicate-doc-alias'] = +'Enable diagnostics for a duplicated alias annotation name.' +config.diagnostics['duplicate-doc-field'] = +'Enable diagnostics for a duplicated field annotation name.' +config.diagnostics['duplicate-doc-param'] = +'Enable diagnostics for a duplicated param annotation name.' +config.diagnostics['duplicate-set-field'] = +'Enable diagnostics for setting the same field in a class more than once.' +config.diagnostics['incomplete-signature-doc'] = +'Incomplete @param or @return annotations for functions.' +config.diagnostics['invisible'] = +'Enable diagnostics for accesses to fields which are invisible.' +config.diagnostics['missing-global-doc'] = +'Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-local-export-doc'] = +'Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-parameter'] = +'Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters.' +config.diagnostics['missing-return'] = +'Enable diagnostics for functions with return annotations which have no return statement.' +config.diagnostics['missing-return-value'] = +'Enable diagnostics for return statements without values although the containing function declares returns.' +config.diagnostics['need-check-nil'] = +'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['no-unknown'] = +'Enable diagnostics for cases in which the type cannot be inferred.' +config.diagnostics['not-yieldable'] = +'Enable diagnostics for calls to `coroutine.yield()` when it is not permitted.' +config.diagnostics['param-type-mismatch'] = +'Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition.' +config.diagnostics['redundant-return'] = +'Enable diagnostics for return statements which are not needed because the function would exit on its own.' +config.diagnostics['redundant-return-value']= +'Enable diagnostics for return statements which return an extra value which is not specified by a return annotation.' +config.diagnostics['return-type-mismatch'] = +'Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation.' +config.diagnostics['spell-check'] = +'Enable diagnostics for typos in strings.' +config.diagnostics['name-style-check'] = +'Enable diagnostics for name style.' +config.diagnostics['unbalanced-assignments']= +'Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`).' +config.diagnostics['undefined-doc-class'] = +'Enable diagnostics for class annotations in which an undefined class is referenced.' +config.diagnostics['undefined-doc-name'] = +'Enable diagnostics for type annotations referencing an undefined type or alias.' +config.diagnostics['undefined-doc-param'] = +'Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition.' +config.diagnostics['undefined-field'] = +'Enable diagnostics for cases in which an undefined field of a variable is read.' +config.diagnostics['unknown-cast-variable'] = +'Enable diagnostics for casts of undefined variables.' +config.diagnostics['unknown-diag-code'] = +'Enable diagnostics in cases in which an unknown diagnostics code is entered.' +config.diagnostics['unknown-operator'] = +'Enable diagnostics for unknown operators.' +config.diagnostics['unreachable-code'] = +'Enable diagnostics for unreachable code.' +config.diagnostics['global-element'] = +'Enable diagnostics to warn about global elements.' +config.typeFormat.config = +'Configures the formatting behavior while typing Lua code.' +config.typeFormat.config.auto_complete_end = +'Controls if `end` is automatically completed at suitable positions.' +config.typeFormat.config.auto_complete_table_sep = +'Controls if a separator is automatically appended at the end of a table declaration.' +config.typeFormat.config.format_line = +'Controls if a line is formatted at all.' + +command.exportDocument = +'Lua: Export Document ...' +command.addon_manager.open = +'Lua: Open Addon Manager ...' +command.reloadFFIMeta = +'Lua: Reload luajit ffi meta' diff --git a/locale/pt-br/meta.lua b/locale/pt-br/meta.lua index f21dac60e..ddd81ce20 100644 --- a/locale/pt-br/meta.lua +++ b/locale/pt-br/meta.lua @@ -519,11 +519,11 @@ math.log10 = 'Retorna o logaritmo `x` na base 10.' math.max = 'Retorna o argumento com o valor mรกximo de acordo com o operador `<`.' -math.maxinteger = +math.maxinteger['>5.3'] = 'Retorna o valor mรกximo para um inteiro.' math.min = 'Retorna o argumento com o valor mรญnimo de acordo com o operador `<`.' -math.mininteger = +math.mininteger['>5.3'] = 'Retorna o valor mรญnimo para um inteiro.' math.modf = 'Retorna a parte inteira e a parte fracionรกria de `x`.' @@ -557,11 +557,11 @@ math.tan = 'Retorna a tangente de `x` (requer valor em radianos).' math.tanh = 'Retorna a tangente hiperbรณlica de `x` (requer valor em radianos).' -math.tointeger = +math.tointeger['>5.3'] = 'Se o valor `x` pode ser convertido para um inteiro, retorna esse inteiro.' -math.type = +math.type['>5.3'] = 'Retorna `"integer"` se `x` รฉ um inteiro, `"float"` se for um valor real (i.e., ponto flutuante), ou `nil` se `x` nรฃo รฉ um nรบmero.' -math.ult = +math.ult['>5.3'] = 'Retorna `true` se e somente se `m` รฉ menor `n` quando eles sรฃo comparados como inteiros sem sinal.' os = diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index 5e954d06f..2357aa50c 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -114,7 +114,23 @@ DIAG_UNDEFINED_DOC_NAME = 'Tipo ou alias indefinido `{}`.' DIAG_UNDEFINED_DOC_PARAM = 'Parรขmetro indefinido `{}`.' -DIAG_UNKNOWN_DIAG_CODE = +DIAG_MISSING_GLOBAL_DOC_COMMENT = -- TODO: need translate! +'Missing comment for global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_PARAM = -- TODO: need translate! +'Missing @param annotation for parameter `{}` in global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_RETURN = -- TODO: need translate! +'Missing @return annotation at index `{}` in global function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT = -- TODO: need translate! +'Missing comment for exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM = -- TODO: need translate! +'Missing @param annotation for parameter `{}` in exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN = -- TODO: need translate! +'Missing @return annotation at index `{}` in exported local function `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM = -- TODO: need translate! +'Incomplete signature. Missing @param annotation for parameter `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN = -- TODO: need translate! +'Incomplete signature. Missing @return annotation at index `{}`.' +DIAG_UNKNOWN_DIAG_CODE = -- TODO: need translate! 'Cรณdigo de diagnรณstico desconhecido `{}`.' DIAG_CAST_LOCAL_TYPE = -- TODO: need translate! 'This variable is defined as type `{def}`. Cannot convert its type to `{ref}`.' @@ -139,11 +155,27 @@ DIAG_REDUNDANT_RETURN_VALUE_RANGE = -- TODO: need translate! DIAG_MISSING_RETURN = -- TODO: need translate! 'Return value is required here.' DIAG_RETURN_TYPE_MISMATCH = -- TODO: need translate! -'The type of the {index} return value is `{def}`, but the actual return is `{ref}`.' +'The type of the {index} return value is `{def}`, but the actual return is `{ref}`.\n{err}' DIAG_UNKNOWN_OPERATOR = -- TODO: need translate! 'Unknown operator `{}`.' DIAG_UNREACHABLE_CODE = -- TODO: need translate! 'Unreachable code.' +DIAG_INVISIBLE_PRIVATE = -- TODO: need translate! +'Field `{field}` is private, it can only be accessed in class `{class}`.' +DIAG_INVISIBLE_PROTECTED = -- TODO: need translate! +'Field `{field}` is protected, it can only be accessed in class `{class}` and its subclasses.' +DIAG_INVISIBLE_PACKAGE = -- TODO: need translate! +'Field `{field}` can only be accessed in same file `{uri}`.' +DIAG_GLOBAL_ELEMENT = -- TODO: need translate! +'Element is global.' +DIAG_MISSING_FIELDS = -- TODO: need translate! +'Missing required fields in type `{1}`: {2}' +DIAG_INJECT_FIELD = -- TODO: need translate! +'Fields cannot be injected into the reference of `{class}` for `{field}`. {fix}' +DIAG_INJECT_FIELD_FIX_CLASS = -- TODO: need translate! +'To do so, use `---@class` for `{node}`.' +DIAG_INJECT_FIELD_FIX_TABLE = -- TODO: need translate! +'ๅฆ‚่ฆๅ…่ฎธๆณจๅ…ฅ๏ผŒ่ฏทๅœจๅฎšไน‰ไธญๆทปๅŠ  `{fix}` ใ€‚' MWS_NOT_SUPPORT = '{} nรฃo รฉ suportado mรบltiplos espaรงos de trabalho por enquanto, posso precisar reiniciar para estabelecer um novo espaรงo de trabalho ...' @@ -171,9 +203,9 @@ WORKSPACE_DIAGNOSTIC = WORKSPACE_SKIP_HUGE_FILE = 'Por motivos de desempenho, a anรกlise deste arquivo foi interrompida: {}' WORKSPACE_NOT_ALLOWED = -'Seu espaรงo de trabalho foi definido para `{}`. Servidor da linguagem Lua recusou o carregamneto neste diretรณrio. Por favor, cheque sua configuraรงรฃo. [aprenda mais aqui](https://github.com/sumneko/lua-language-server/wiki/FAQ#why-is-the-server-scanning-the-wrong-folder)' +'Seu espaรงo de trabalho foi definido para `{}`. Servidor da linguagem Lua recusou o carregamneto neste diretรณrio. Por favor, cheque sua configuraรงรฃo. [aprenda mais aqui](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder)' WORKSPACE_SCAN_TOO_MUCH = -- TODO: need translate! -'Mais do que {} arquivos foram escaneados. O diretรณrio atual escaneado รฉ `{}`. Por favor, confirmar se a configuraรงรฃo estรก correta' +'Mais do que {} arquivos foram escaneados. O diretรณrio atual escaneado รฉ `{}`. Please see the [FAQ](https://luals.github.io/wiki/faq#how-can-i-improve-startup-speeds) to see how you can include fewer files. It is also possible that your [configuration is incorrect](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder).' PARSER_CRASH = 'Parser quebrou! รšltimas palavras: {}' @@ -269,6 +301,14 @@ PARSER_INDEX_IN_FUNC_NAME = 'A forma `[name]` nรฃo pode ser usada em nome de uma funรงรฃo nomeada.' PARSER_UNKNOWN_ATTRIBUTE = 'Atributo local deve ser `const` ou `close`' +PARSER_AMBIGUOUS_SYNTAX = -- TODO: need translate! +'In Lua 5.1, the left brackets called by the function must be in the same line as the function.' +PARSER_NEED_PAREN = -- TODO: need translate! +'้œ€่ฆๆทปๅŠ ไธ€ๅฏนๆ‹ฌๅทใ€‚' +PARSER_NESTING_LONG_MARK = -- TODO: need translate! +'Nesting of `[[...]]` is not allowed in Lua 5.1 .' +PARSER_LOCAL_LIMIT = -- TODO: need translate! +'Only 200 active local variables and upvalues can be existed at the same time.' PARSER_LUADOC_MISS_CLASS_NAME = 'Esperado .' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = @@ -414,6 +454,10 @@ ACTION_MARK_ASYNC = 'Marque a funรงรฃo atual como assรญncrona' ACTION_ADD_DICT = 'Adicione \'{}\' ao seu espaรงo de trabalho no ' +ACTION_FIX_ADD_PAREN = -- TODO: need translate! +'ๆทปๅŠ ๆ‹ฌๅทใ€‚' +ACTION_AUTOREQUIRE = -- TODO: need translate! +"Import '{}' as {}" COMMAND_DISABLE_DIAG = 'Desativar diagnรณsticos.' @@ -435,6 +479,8 @@ COMMAND_JSON_TO_LUA_FAILED = 'Converรงรฃo de JSON para Lua falhou: {}.' COMMAND_ADD_DICT = 'Adicione uma palavra ao dicionรกrio' +COMMAND_REFERENCE_COUNT = -- TODO: need translate! +'{} references' COMPLETION_IMPORT_FROM = 'Importa de {}.' @@ -512,7 +558,7 @@ WINDOW_APPLY_SETTING = WINDOW_CHECK_SEMANTIC = 'Se vocรช estiver usando o tema de cores do market, talvez seja necessรกrio modificar `editor.semanticHighlighting.enabled` para `true` para fazer com tokens semรขnticas sejam habilitados.' WINDOW_TELEMETRY_HINT = -'Por favor, permita o envio de dados de uso e relatรณrios de erro anรดnimos para nos ajudar a melhorar ainda mais essa extensรฃo. Leia nossa polรญtica de privacidade [aqui](https://github.com/sumneko/lua-language-server/wiki/Home#privacy) .' +'Por favor, permita o envio de dados de uso e relatรณrios de erro anรดnimos para nos ajudar a melhorar ainda mais essa extensรฃo. Leia nossa polรญtica de privacidade [aqui](https://luals.github.io/privacy/#language-server) .' WINDOW_TELEMETRY_ENABLE = 'Permitir' WINDOW_TELEMETRY_DISABLE = @@ -535,6 +581,14 @@ WINDOW_ASK_APPLY_LIBRARY = 'Vocรช precisa configurar seu ambiente de trabalho como `{}`?' WINDOW_SEARCHING_IN_FILES = -- TODO: need translate! 'Procurando nos arquivos...' +WINDOW_CONFIG_LUA_DEPRECATED = -- TODO: need translate! +'`config.lua` is deprecated, please use `config.json` instead.' +WINDOW_CONVERT_CONFIG_LUA = -- TODO: need translate! +'Convert to `config.json`' +WINDOW_MODIFY_REQUIRE_PATH = -- TODO: need translate! +'Do you want to modify the require path?' +WINDOW_MODIFY_REQUIRE_OK = -- TODO: need translate! +'Modify' CONFIG_LOAD_FAILED = 'Nรฃo รฉ possรญvel ler o arquivo de configuraรงรตes: {}' @@ -542,6 +596,24 @@ CONFIG_LOAD_ERROR = 'Configurando o erro de carregamento do arquivo: {}' CONFIG_TYPE_ERROR = 'O arquivo de configuraรงรฃo deve estar no formato LUA ou JSON: {}' +CONFIG_MODIFY_FAIL_SYNTAX_ERROR = -- TODO: need translate! +'Failed to modify settings, there are syntax errors in the settings file: {}' +CONFIG_MODIFY_FAIL_NO_WORKSPACE = -- TODO: need translate! +[[ +Failed to modify settings: +* The current mode is single-file mode, server cannot create `.luarc.json` without workspace. +* The language client dose not support modifying settings from the server side. + +Please modify following settings manually: +{} +]] +CONFIG_MODIFY_FAIL = -- TODO: need translate! +[[ +Failed to modify settings + +Please modify following settings manually: +{} +]] PLUGIN_RUNTIME_ERROR = [[ @@ -576,6 +648,49 @@ CLI_CHECK_SUCCESS = 'Diagnรณstico completo, nenhum problema encontrado' CLI_CHECK_RESULTS = 'Diagnรณstico completo, {} problemas encontrados, veja {}' +CLI_DOC_INITING = -- TODO: need translate! +'Loading documents ...' +CLI_DOC_DONE = -- TODO: need translate! +[[ +Document exporting completed! +Raw data: {} +Markdown(example): {} +]] + +TYPE_ERROR_ENUM_GLOBAL_DISMATCH = -- TODO: need translate! +'Type `{child}` cannot match enumeration type of `{parent}`' +TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED = -- TODO: need translate! +'Cannot use generic `{child}` in enumeration' +TYPE_ERROR_ENUM_LITERAL_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match the enumeration value of `{parent}`' +TYPE_ERROR_ENUM_OBJECT_DISMATCH = -- TODO: need translate! +'The object `{child}` cannot match the enumeration value of `{parent}`. They must be the same object' +TYPE_ERROR_ENUM_NO_OBJECT = -- TODO: need translate! +'The passed in enumeration value `{child}` is not recognized' +TYPE_ERROR_INTEGER_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match integer `{parent}`' +TYPE_ERROR_STRING_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match string `{parent}`' +TYPE_ERROR_BOOLEAN_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match boolean `{parent}`' +TYPE_ERROR_TABLE_NO_FIELD = -- TODO: need translate! +'Field `{key}` does not exist in the table' +TYPE_ERROR_TABLE_FIELD_DISMATCH = -- TODO: need translate! +'The type of field `{key}` is `{child}`, which cannot match `{parent}`' +TYPE_ERROR_CHILD_ALL_DISMATCH = -- TODO: need translate! +'All subtypes in `{child}` cannot match `{parent}`' +TYPE_ERROR_PARENT_ALL_DISMATCH = -- TODO: need translate! +'`{child}` cannot match any subtypes in `{parent}`' +TYPE_ERROR_UNION_DISMATCH = -- TODO: need translate! +'`{child}` cannot match `{parent}`' +TYPE_ERROR_OPTIONAL_DISMATCH = -- TODO: need translate! +'Optional type cannot match `{parent}`' +TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = -- TODO: need translate! +'The number `{child}` cannot be converted to an integer' +TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = -- TODO: need translate! +'Cannot convert number type to integer type' +TYPE_ERROR_DISMATCH = -- TODO: need translate! +'Type `{child}` cannot match `{parent}`' LUADOC_DESC_CLASS = -- TODO: need translate! [=[ @@ -588,7 +703,7 @@ Defines a class/table structure Manager = {} ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#class) +[View Wiki](https://luals.github.io/wiki/annotations#class) ]=] LUADOC_DESC_TYPE = -- TODO: need translate! [=[ @@ -639,7 +754,7 @@ local x --x[""] is true local myFunction ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#types-and-type) +[View Wiki](https://luals.github.io/wiki/annotations#type) ]=] LUADOC_DESC_ALIAS = -- TODO: need translate! [=[ @@ -674,8 +789,22 @@ function find(path, pattern) end ---@param style font-style Style to apply function setFontStyle(style) end ``` + +### Literal Enum +``` +local enums = { + READ = 0, + WRITE = 1, + CLOSED = 2 +} + +---@alias FileStates +---| `enums.READ` +---| `enums.WRITE` +---| `enums.CLOSE` +``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +[View Wiki](https://luals.github.io/wiki/annotations#alias) ]=] LUADOC_DESC_PARAM = -- TODO: need translate! [=[ @@ -700,7 +829,7 @@ function get(url, headers, timeout) end function concat(base, ...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#param) +[View Wiki](https://luals.github.io/wiki/annotations#param) ]=] LUADOC_DESC_RETURN = -- TODO: need translate! [=[ @@ -738,12 +867,13 @@ function getFirstLast() end function getTags(item) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#return) +[View Wiki](https://luals.github.io/wiki/annotations#return) ]=] LUADOC_DESC_FIELD = -- TODO: need translate! [=[ Declare a field in a class/table. This allows you to provide more in-depth -documentation for a table. +documentation for a table. As of `v3.6.0`, you can mark a field as `private`, +`protected`, `public`, or `package`. ## Syntax `---@field [description]` @@ -768,7 +898,7 @@ response = get("localhost") statusCode = response.status.code ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#field) +[View Wiki](https://luals.github.io/wiki/annotations#field) ]=] LUADOC_DESC_GENERIC = -- TODO: need translate! [=[ @@ -825,7 +955,7 @@ local v = Generic("Foo") -- v is an object of Foo -- we give for key (K) or value (V) ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#generics-and-generic) +[View Wiki](https://luals.github.io/wiki/annotations/#generic) ]=] LUADOC_DESC_VARARG = -- TODO: need translate! [=[ @@ -844,7 +974,7 @@ provide typing or allow descriptions. function concat(...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#vararg) +[View Wiki](https://luals.github.io/wiki/annotations#vararg) ]=] LUADOC_DESC_OVERLOAD = -- TODO: need translate! [=[ @@ -859,7 +989,7 @@ Allows defining of multiple function signatures. function table.insert(t, position, value) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#overload) +[View Wiki](https://luals.github.io/wiki/annotations#overload) ]=] LUADOC_DESC_DEPRECATED = -- TODO: need translate! [=[ @@ -870,7 +1000,7 @@ being ~~struck through~~. `---@deprecated` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#deprecated) +[View Wiki](https://luals.github.io/wiki/annotations#deprecated) ]=] LUADOC_DESC_META = -- TODO: need translate! [=[ @@ -885,7 +1015,7 @@ There are 3 main distinctions to note with meta files: `---@meta` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#meta) +[View Wiki](https://luals.github.io/wiki/annotations#meta) ]=] LUADOC_DESC_VERSION = -- TODO: need translate! [=[ @@ -910,7 +1040,7 @@ function onlyWorksInJIT() end function oldLuaOnly() end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#version) +[View Wiki](https://luals.github.io/wiki/annotations#version) ]=] LUADOC_DESC_SEE = -- TODO: need translate! [=[ @@ -920,7 +1050,7 @@ Define something that can be viewed for more information `---@see ` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#see) +[View Wiki](https://luals.github.io/wiki/annotations#see) ]=] LUADOC_DESC_DIAGNOSTIC = -- TODO: need translate! [=[ @@ -928,7 +1058,7 @@ Enable/disable diagnostics for error/warnings/etc. Actions: `disable`, `enable`, `disable-line`, `disable-next-line` -[Names](https://github.com/sumneko/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) +[Names](https://github.com/LuaLS/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) ## Syntax `---@diagnostic [: ]` @@ -946,7 +1076,7 @@ local unused = "hello world" ---@diagnostic enable: unused-local ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#diagnostic) +[View Wiki](https://luals.github.io/wiki/annotations#diagnostic) ]=] LUADOC_DESC_MODULE = -- TODO: need translate! [=[ @@ -963,7 +1093,7 @@ local stringUtils local module = require('string.utils') ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#module) +[View Wiki](https://luals.github.io/wiki/annotations#module) ]=] LUADOC_DESC_ASYNC = -- TODO: need translate! [=[ @@ -973,7 +1103,7 @@ Marks a function as asynchronous. `---@async` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#async) +[View Wiki](https://luals.github.io/wiki/annotations#async) ]=] LUADOC_DESC_NODISCARD = -- TODO: need translate! [=[ @@ -985,7 +1115,7 @@ be ignored. `---@nodiscard` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#nodiscard) +[View Wiki](https://luals.github.io/wiki/annotations#nodiscard) ]=] LUADOC_DESC_CAST = -- TODO: need translate! [=[ @@ -1020,7 +1150,7 @@ local x --> string|table print(x) --> table ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#cast) +[View Wiki](https://luals.github.io/wiki/annotations#cast) ]=] LUADOC_DESC_OPERATOR = -- TODO: need translate! [=[ @@ -1050,12 +1180,12 @@ pA = Passcode.new(1234) pB = -pA --> integer ``` -[View Request](https://github.com/sumneko/lua-language-server/issues/599) +[View Request](https://github.com/LuaLS/lua-language-server/issues/599) ]=] LUADOC_DESC_ENUM = -- TODO: need translate! [=[ Mark a table as an enum. If you want an enum but can't define it as a Lua -table, take a look at the [`@alias`](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +table, take a look at the [`@alias`](https://luals.github.io/wiki/annotations#alias) tag. ## Syntax @@ -1079,3 +1209,103 @@ local function setColor(color) end setColor(colors.green) ``` ]=] +LUADOC_DESC_SOURCE = -- TODO: need translate! +[=[ +Provide a reference to some source code which lives in another file. When +searching for the defintion of an item, its `@source` will be used. + +## Syntax +`@source ` + +## Usage +``` +---You can use absolute paths +---@source C:/Users/me/Documents/program/myFile.c +local a + +---Or URIs +---@source file:///C:/Users/me/Documents/program/myFile.c:10 +local b + +---Or relative paths +---@source local/file.c +local c + +---You can also include line and char numbers +---@source local/file.c:10:8 +local d +``` +]=] +LUADOC_DESC_PACKAGE = -- TODO: need translate! +[=[ +Mark a function as private to the file it is defined in. A packaged function +cannot be accessed from another file. + +## Syntax +`@package` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@package +---This cannot be accessed in another file +function Animal:eyesCount() + return self.eyes +end +``` +]=] +LUADOC_DESC_PRIVATE = -- TODO: need translate! +[=[ +Mark a function as private to a @class. Private functions can be accessed only +from within their class and are not accessable from child classes. + +## Syntax +`@private` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@private +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---NOT PERMITTED! +myDog:eyesCount(); +``` +]=] +LUADOC_DESC_PROTECTED = -- TODO: need translate! +[=[ +Mark a function as protected within a @class. Protected functions can be +accessed only from within their class or from child classes. + +## Syntax +`@protected` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@protected +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---Permitted because function is protected, not private. +myDog:eyesCount(); +``` +]=] diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua index 79c3770be..6ececcd34 100644 --- a/locale/pt-br/setting.lua +++ b/locale/pt-br/setting.lua @@ -1,5 +1,7 @@ ---@diagnostic disable: undefined-global +config.addonManager.enable = -- TODO: need translate! +"Whether the addon manager is enabled or not." config.runtime.version = -- TODO: need translate! "Lua runtime version." config.runtime.path = -- TODO: need translate! @@ -25,7 +27,7 @@ config.runtime.unicodeName = -- TODO: need translate! config.runtime.nonstandardSymbol = -- TODO: need translate! "Supports non-standard symbols. Make sure that your runtime environment supports these symbols." config.runtime.plugin = -- TODO: need translate! -"Plugin path. Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/Plugins) to learn more." +"Plugin path. Please read [wiki](https://luals.github.io/wiki/plugins) to learn more." config.runtime.pluginArgs = -- TODO: need translate! "Additional arguments for the plugin." config.runtime.fileEncoding = -- TODO: need translate! @@ -77,8 +79,16 @@ Modify the diagnostic needed file status in a group. `Fallback` means that diagnostics in this group are controlled by `diagnostics.neededFileStatus` separately. Other settings will override individual settings without end of `!`. ]] +config.diagnostics.workspaceEvent = -- TODO: need translate! +"Set the time to trigger workspace diagnostics." +config.diagnostics.workspaceEvent.OnChange = -- TODO: need translate! +"Trigger workspace diagnostics when the file is changed." +config.diagnostics.workspaceEvent.OnSave = -- TODO: need translate! +"Trigger workspace diagnostics when the file is saved." +config.diagnostics.workspaceEvent.None = -- TODO: need translate! +"Disable workspace diagnostics." config.diagnostics.workspaceDelay = -- TODO: need translate! -"Latency (milliseconds) for workspace diagnostics. When you start the workspace, or edit any file, the entire workspace will be re-diagnosed in the background. Set to negative to disable workspace diagnostics." +"Latency (milliseconds) for workspace diagnostics." config.diagnostics.workspaceRate = -- TODO: need translate! "Workspace diagnostics run rate (%). Decreasing this value reduces CPU usage, but also reduces the speed of workspace diagnostics. The diagnosis of the file you are currently editing is always done at full speed and is not affected by this setting." config.diagnostics.libraryFiles = -- TODO: need translate! @@ -125,7 +135,7 @@ Automatic detection and adaptation of third-party libraries, currently supported * Jass ]] config.workspace.userThirdParty = -- TODO: need translate! -'Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd)' +'Add private third-party library configuration file paths here, please refer to the built-in [configuration file path](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd)' config.workspace.supportScheme = -- TODO: need translate! 'Provide language server for the Lua files of the following scheme.' config.completion.enable = -- TODO: need translate! @@ -248,6 +258,8 @@ config.hint.semicolon.SameLine = -- TODO: need translate! 'When two statements are on the same line, display a semicolon between them.' config.hint.semicolon.Disable = -- TODO: need translate! 'Disable virtual semicolons.' +config.codeLens.enable = -- TODO: need translate! +'Enable code lens.' config.format.enable = -- TODO: need translate! 'Enable code formatter.' config.format.defaultConfig = -- TODO: need translate! @@ -257,20 +269,16 @@ Read [formatter docs](https://github.com/CppCXY/EmmyLuaCodeStyle/tree/master/doc ]] config.spell.dict = -- TODO: need translate! 'Custom words for spell checking.' +config.nameStyle.config = -- TODO: need translate! +'Set name style config' config.telemetry.enable = -- TODO: need translate! [[ -Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://github.com/sumneko/lua-language-server/wiki/Home#privacy). +Enable telemetry to send your editor information and error logs over the network. Read our privacy policy [here](https://luals.github.io/privacy/#language-server). ]] config.misc.parameters = -- TODO: need translate! -'[Command line parameters](https://github.com/sumneko/lua-telemetry-server/tree/master/method) when starting the language service in VSCode.' -config.IntelliSense.traceLocalSet = -- TODO: need translate! -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceReturn = -- TODO: need translate! -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceBeSetted = -- TODO: need translate! -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' -config.IntelliSense.traceFieldInject = -- TODO: need translate! -'Please read [wiki](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features) to learn more.' +'[Command line parameters](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) when starting the language service in VSCode.' +config.misc.executablePath = -- TODO: need translate! +'Specify the executable path in VSCode.' config.type.castNumberToInteger = -- TODO: need translate! 'Allowed to assign the `number` type to the `integer` type.' config.type.weakUnionCheck = -- TODO: need translate! @@ -285,6 +293,18 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = -- TODO: need translate! +[[ +When the parameter type is not annotated, the parameter type is inferred from the function's incoming parameters. + +When this setting is `false`, the type of the parameter is `any` when it is not annotated. +]] +config.doc.privateName = -- TODO: need translate! +'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' +config.doc.protectedName = -- TODO: need translate! +'Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses.' +config.doc.packageName = -- TODO: need translate! +'Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located.' config.diagnostics['unused-local'] = -- TODO: need translate! 'ๆœชไฝฟ็”จ็š„ๅฑ€้ƒจๅ˜้‡' config.diagnostics['unused-function'] = -- TODO: need translate! @@ -319,3 +339,104 @@ config.diagnostics['empty-block'] = -- TODO: need translate! '็ฉบไปฃ็ ๅ—' config.diagnostics['redundant-value'] = -- TODO: need translate! '่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš' +config.diagnostics['assign-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for assignments in which the value\'s type does not match the type of the assigned variable.' +config.diagnostics['await-in-sync'] = -- TODO: need translate! +'Enable diagnostics for calls of asynchronous functions within a synchronous function.' +config.diagnostics['cast-local-type'] = -- TODO: need translate! +'Enable diagnostics for casts of local variables where the target type does not match the defined type.' +config.diagnostics['cast-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for casts where the target type does not match the initial type.' +config.diagnostics['circular-doc-class'] = -- TODO: need translate! +'Enable diagnostics for two classes inheriting from each other introducing a circular relation.' +config.diagnostics['close-non-object'] = -- TODO: need translate! +'Enable diagnostics for attempts to close a variable with a non-object.' +config.diagnostics['code-after-break'] = -- TODO: need translate! +'Enable diagnostics for code placed after a break statement in a loop.' +config.diagnostics['codestyle-check'] = -- TODO: need translate! +'Enable diagnostics for incorrectly styled lines.' +config.diagnostics['count-down-loop'] = -- TODO: need translate! +'Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing.' +config.diagnostics['deprecated'] = -- TODO: need translate! +'Enable diagnostics to highlight deprecated API.' +config.diagnostics['different-requires'] = -- TODO: need translate! +'Enable diagnostics for files which are required by two different paths.' +config.diagnostics['discard-returns'] = -- TODO: need translate! +'Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored.' +config.diagnostics['doc-field-no-class'] = -- TODO: need translate! +'Enable diagnostics to highlight a field annotation without a defining class annotation.' +config.diagnostics['duplicate-doc-alias'] = -- TODO: need translate! +'Enable diagnostics for a duplicated alias annotation name.' +config.diagnostics['duplicate-doc-field'] = -- TODO: need translate! +'Enable diagnostics for a duplicated field annotation name.' +config.diagnostics['duplicate-doc-param'] = -- TODO: need translate! +'Enable diagnostics for a duplicated param annotation name.' +config.diagnostics['duplicate-set-field'] = -- TODO: need translate! +'Enable diagnostics for setting the same field in a class more than once.' +config.diagnostics['incomplete-signature-doc'] = -- TODO: need translate! +'Incomplete @param or @return annotations for functions.' +config.diagnostics['invisible'] = -- TODO: need translate! +'Enable diagnostics for accesses to fields which are invisible.' +config.diagnostics['missing-global-doc'] = -- TODO: need translate! +'Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-local-export-doc'] = -- TODO: need translate! +'Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-parameter'] = -- TODO: need translate! +'Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters.' +config.diagnostics['missing-return'] = -- TODO: need translate! +'Enable diagnostics for functions with return annotations which have no return statement.' +config.diagnostics['missing-return-value'] = -- TODO: need translate! +'Enable diagnostics for return statements without values although the containing function declares returns.' +config.diagnostics['need-check-nil'] = -- TODO: need translate! +'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['no-unknown'] = -- TODO: need translate! +'Enable diagnostics for cases in which the type cannot be inferred.' +config.diagnostics['not-yieldable'] = -- TODO: need translate! +'Enable diagnostics for calls to `coroutine.yield()` when it is not permitted.' +config.diagnostics['param-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition.' +config.diagnostics['redundant-return'] = -- TODO: need translate! +'Enable diagnostics for return statements which are not needed because the function would exit on its own.' +config.diagnostics['redundant-return-value']= -- TODO: need translate! +'Enable diagnostics for return statements which return an extra value which is not specified by a return annotation.' +config.diagnostics['return-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation.' +config.diagnostics['spell-check'] = -- TODO: need translate! +'Enable diagnostics for typos in strings.' +config.diagnostics['name-style-check'] = -- TODO: need translate! +'Enable diagnostics for name style.' +config.diagnostics['unbalanced-assignments']= -- TODO: need translate! +'Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`).' +config.diagnostics['undefined-doc-class'] = -- TODO: need translate! +'Enable diagnostics for class annotations in which an undefined class is referenced.' +config.diagnostics['undefined-doc-name'] = -- TODO: need translate! +'Enable diagnostics for type annotations referencing an undefined type or alias.' +config.diagnostics['undefined-doc-param'] = -- TODO: need translate! +'Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition.' +config.diagnostics['undefined-field'] = -- TODO: need translate! +'Enable diagnostics for cases in which an undefined field of a variable is read.' +config.diagnostics['unknown-cast-variable'] = -- TODO: need translate! +'Enable diagnostics for casts of undefined variables.' +config.diagnostics['unknown-diag-code'] = -- TODO: need translate! +'Enable diagnostics in cases in which an unknown diagnostics code is entered.' +config.diagnostics['unknown-operator'] = -- TODO: need translate! +'Enable diagnostics for unknown operators.' +config.diagnostics['unreachable-code'] = -- TODO: need translate! +'Enable diagnostics for unreachable code.' +config.diagnostics['global-element'] = -- TODO: need translate! +'Enable diagnostics to warn about global elements.' +config.typeFormat.config = -- TODO: need translate! +'Configures the formatting behavior while typing Lua code.' +config.typeFormat.config.auto_complete_end = -- TODO: need translate! +'Controls if `end` is automatically completed at suitable positions.' +config.typeFormat.config.auto_complete_table_sep = -- TODO: need translate! +'Controls if a separator is automatically appended at the end of a table declaration.' +config.typeFormat.config.format_line = -- TODO: need translate! +'Controls if a line is formatted at all.' + +command.exportDocument = -- TODO: need translate! +'Lua: Export Document ...' +command.addon_manager.open = -- TODO: need translate! +'Lua: Open Addon Manager ...' +command.reloadFFIMeta = -- TODO: need translate! +'Lua: Reload luajit ffi meta' diff --git a/locale/zh-cn/meta.lua b/locale/zh-cn/meta.lua index c4b5fbb65..64d04ae5d 100644 --- a/locale/zh-cn/meta.lua +++ b/locale/zh-cn/meta.lua @@ -497,11 +497,11 @@ math.log10 = '่ฟ”ๅ›ž `x` ็š„ไปฅ10ไธบๅบ•็š„ๅฏนๆ•ฐใ€‚' math.max = '่ฟ”ๅ›žๅ‚ๆ•ฐไธญๆœ€ๅคง็š„ๅ€ผ๏ผŒ ๅคงๅฐ็”ฑ Lua ๆ“ไฝœ `<` ๅ†ณๅฎšใ€‚' -math.maxinteger = +math.maxinteger['>5.3'] = 'ๆœ€ๅคงๅ€ผ็š„ๆ•ดๆ•ฐใ€‚' math.min = '่ฟ”ๅ›žๅ‚ๆ•ฐไธญๆœ€ๅฐ็š„ๅ€ผ๏ผŒ ๅคงๅฐ็”ฑ Lua ๆ“ไฝœ `<` ๅ†ณๅฎšใ€‚' -math.mininteger = +math.mininteger['>5.3'] = 'ๆœ€ๅฐๅ€ผ็š„ๆ•ดๆ•ฐใ€‚' math.modf = '่ฟ”ๅ›ž `x` ็š„ๆ•ดๆ•ฐ้ƒจๅˆ†ๅ’Œๅฐๆ•ฐ้ƒจๅˆ†ใ€‚' @@ -535,11 +535,11 @@ math.tan = '่ฟ”ๅ›ž `x` ็š„ๆญฃๅˆ‡ๅ€ผ๏ผˆๅ‡ๅฎšๅ‚ๆ•ฐๆ˜ฏๅผงๅบฆ๏ผ‰ใ€‚' math.tanh = '่ฟ”ๅ›ž `x` ็š„ๅŒๆ›ฒๆญฃๅˆ‡ๅ€ผ๏ผˆๅ‡ๅฎšๅ‚ๆ•ฐๆ˜ฏๅผงๅบฆ๏ผ‰ใ€‚' -math.tointeger = +math.tointeger['>5.3'] = 'ๅฆ‚ๆžœ `x` ๅฏไปฅ่ฝฌๆขไธบไธ€ไธชๆ•ดๆ•ฐ๏ผŒ ่ฟ”ๅ›ž่ฏฅๆ•ดๆ•ฐใ€‚' -math.type = +math.type['>5.3'] = 'ๅฆ‚ๆžœ `x` ๆ˜ฏๆ•ดๆ•ฐ๏ผŒ่ฟ”ๅ›ž `"integer"`๏ผŒ ๅฆ‚ๆžœๅฎƒๆ˜ฏๆตฎ็‚นๆ•ฐ๏ผŒ่ฟ”ๅ›ž `"float"`๏ผŒ ๅฆ‚ๆžœ `x` ไธๆ˜ฏๆ•ฐๅญ—๏ผŒ่ฟ”ๅ›ž `nil`ใ€‚' -math.ult = +math.ult['>5.3'] = 'ๅฆ‚ๆžœๆ•ดๆ•ฐ `m` ๅ’Œ `n` ไปฅๆ— ็ฌฆๅทๆ•ดๆ•ฐๅฝขๅผๆฏ”่พƒ๏ผŒ `m` ๅœจ `n` ไน‹ไธ‹๏ผŒ่ฟ”ๅ›žๅธƒๅฐ”็œŸๅฆๅˆ™่ฟ”ๅ›žๅ‡ใ€‚' os = diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index e839e92c4..6459a104d 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -114,6 +114,22 @@ DIAG_UNDEFINED_DOC_NAME = 'ๆœชๅฎšไน‰็š„็ฑปๅž‹ๆˆ–ๅˆซๅ `{}`ใ€‚' DIAG_UNDEFINED_DOC_PARAM = 'ๆŒ‡ๅ‘ไบ†ๆœชๅฎšไน‰็š„ๅ‚ๆ•ฐ `{}`ใ€‚' +DIAG_MISSING_GLOBAL_DOC_COMMENT = +'ๅ…จๅฑ€ๅ‡ฝๆ•ฐ `{}` ็ผบๅฐ‘ๆณจ้‡Šใ€‚' +DIAG_MISSING_GLOBAL_DOC_PARAM = +'ๅ…จๅฑ€ๅ‡ฝๆ•ฐ `{2}` ็š„ๅ‚ๆ•ฐ `{1}` ็ผบๅฐ‘ @param ๆณจ่งฃใ€‚' +DIAG_MISSING_GLOBAL_DOC_RETURN = +'ๅ…จๅฑ€ๅ‡ฝๆ•ฐ `{2}` ็š„็ฌฌ `{1}` ไธช่ฟ”ๅ›žๅ€ผ็ผบๅฐ‘ @return ๆณจ่งฃใ€‚' +DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT = +'ๅฏผๅ‡บ็š„ๅฑ€้ƒจๅ‡ฝๆ•ฐ `{}` ็ผบๅฐ‘ๆณจ้‡Šใ€‚' +DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM = +'ๅฏผๅ‡บ็š„ๅฑ€้ƒจๅ‡ฝๆ•ฐ `{2}` ็š„ๅ‚ๆ•ฐ `{1}` ็ผบๅฐ‘ @param ๆณจ่งฃใ€‚' +DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN = +'ๅฏผๅ‡บ็š„ๅฑ€้ƒจๅ‡ฝๆ•ฐ `{2}` ็š„็ฌฌ {1} ไธช่ฟ”ๅ›žๅ€ผ็ผบๅฐ‘ @return ๆณจ่งฃใ€‚' +DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM = +'็ญพๅไธๅฎŒๆ•ดใ€‚ๅ‚ๆ•ฐ `{1}` ็ผบๅฐ‘ @param ๆณจ่งฃใ€‚' +DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN = +'็ญพๅไธๅฎŒๆ•ดใ€‚็ฌฌ {1} ไธช่ฟ”ๅ›žๅ€ผ็ผบๅฐ‘ @return ๆณจ่งฃใ€‚' DIAG_UNKNOWN_DIAG_CODE = 'ๆœช็Ÿฅ็š„่ฏŠๆ–ญไปฃๅท `{}`ใ€‚' DIAG_CAST_LOCAL_TYPE = @@ -144,6 +160,22 @@ DIAG_UNKNOWN_OPERATOR = 'ๆœช็Ÿฅ็š„่ฟ็ฎ—็ฌฆ `{}`ใ€‚' DIAG_UNREACHABLE_CODE = 'ไธๅฏ่พพ็š„ไปฃ็ ใ€‚' +DIAG_INVISIBLE_PRIVATE = +'ๅญ—ๆฎต `{field}` ๆ˜ฏ็งๆœ‰็š„๏ผŒๅช่ƒฝๅœจ `{class}` ็ฑปไธญๆ‰่ƒฝ่ฎฟ้—ฎใ€‚' +DIAG_INVISIBLE_PROTECTED = +'ๅญ—ๆฎต `{field}` ๅ—ๅˆฐไฟๆŠค๏ผŒๅช่ƒฝๅœจ `{class}` ็ฑปๆžๅ…ถๅญ็ฑปไธญๆ‰่ƒฝ่ฎฟ้—ฎใ€‚' +DIAG_INVISIBLE_PACKAGE = +'ๅญ—ๆฎต `{field}` ๅช่ƒฝๅœจ็›ธๅŒ็š„ๆ–‡ไปถ `{uri}` ไธญๆ‰่ƒฝ่ฎฟ้—ฎใ€‚' +DIAG_GLOBAL_ELEMENT = +'ๅ…จๅฑ€ๅ˜้‡ใ€‚' +DIAG_MISSING_FIELDS = +'็ผบๅฐ‘็ฑปๅž‹ `{1}` ็š„ๅฟ…่ฆๅญ—ๆฎต๏ผš {2}' +DIAG_INJECT_FIELD = +'ไธ่ƒฝๅœจ `{class}` ็š„ๅผ•็”จไธญๆณจๅ…ฅๅญ—ๆฎต `{field}` ใ€‚{fix}' +DIAG_INJECT_FIELD_FIX_CLASS = +'ๅฆ‚่ฆๅ…่ฎธๆณจๅ…ฅ๏ผŒ่ฏทๅฏน `{node}` ไฝฟ็”จ `{fix}` ใ€‚' +DIAG_INJECT_FIELD_FIX_TABLE = +'ๅฆ‚่ฆๅ…่ฎธๆณจๅ…ฅ๏ผŒ่ฏทๅœจๅฎšไน‰ไธญๆทปๅŠ  `{fix}` ใ€‚' MWS_NOT_SUPPORT = '{} ็›ฎๅ‰่ฟ˜ไธๆ”ฏๆŒๅคšๅทฅไฝœ็›ฎๅฝ•๏ผŒๆˆ‘ๅฏ่ƒฝ้œ€่ฆ้‡ๅฏๆ‰่ƒฝๆ”ฏๆŒๆ–ฐ็š„ๅทฅไฝœ็›ฎๅฝ•...' @@ -171,9 +203,9 @@ WORKSPACE_DIAGNOSTIC = WORKSPACE_SKIP_HUGE_FILE = 'ๅ‡บไบŽๆ€ง่ƒฝ่€ƒ่™‘๏ผŒๅทฒๅœๆญขๅฏนๆญคๆ–‡ไปถ่งฃๆž๏ผš{}' WORKSPACE_NOT_ALLOWED = -'ไฝ ็š„ๅทฅไฝœ็›ฎๅฝ•่ขซ่ฎพ็ฝฎไธบไบ† `{}`๏ผŒLua่ฏญ่จ€ๆœๅŠกๆ‹’็ปๅŠ ่ฝฝๆญค็›ฎๅฝ•๏ผŒ่ฏทๆฃ€ๆŸฅไฝ ็š„้…็ฝฎใ€‚[ไบ†่งฃๆ›ดๅคš](https://github.com/sumneko/lua-language-server/wiki/FAQ#why-is-the-server-scanning-the-wrong-folder)' +'ไฝ ็š„ๅทฅไฝœ็›ฎๅฝ•่ขซ่ฎพ็ฝฎไธบไบ† `{}`๏ผŒLua่ฏญ่จ€ๆœๅŠกๆ‹’็ปๅŠ ่ฝฝๆญค็›ฎๅฝ•๏ผŒ่ฏทๆฃ€ๆŸฅไฝ ็š„้…็ฝฎใ€‚[ไบ†่งฃๆ›ดๅคš](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder)' WORKSPACE_SCAN_TOO_MUCH = -'ๅทฒๆ‰ซๆไบ†่ถ…่ฟ‡ {} ไธชๆ–‡ไปถ๏ผŒๅฝ“ๅ‰ๆ‰ซๆ็š„็›ฎๅฝ•ไธบ `{}`๏ผŒ่ฏท็กฎ่ฎค้…็ฝฎๆ˜ฏๅฆๆญฃ็กฎใ€‚' +'ๅทฒๆ‰ซๆไบ†่ถ…่ฟ‡ {} ไธชๆ–‡ไปถ๏ผŒๅฝ“ๅ‰ๆ‰ซๆ็š„็›ฎๅฝ•ไธบ `{}`. ่ฏทๅ‚้˜… [FAQ](https://luals.github.io/wiki/faq#how-can-i-improve-startup-speeds) ไบ†่งฃๅฆ‚ไฝ•ๆŽ’้™คๅคšไฝ™็š„ๆ–‡ไปถใ€‚ไนŸๅฏ่ƒฝๆ˜ฏไฝ ็š„ [่ฎพ็ฝฎๆœ‰้”™่ฏฏ](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder).' PARSER_CRASH = '่ฏญๆณ•่งฃๆžๅดฉๆบƒไบ†๏ผ้—่จ€๏ผš{}' @@ -269,6 +301,14 @@ PARSER_INDEX_IN_FUNC_NAME = 'ๅ‘ฝๅๅ‡ฝๆ•ฐ็š„ๅ็งฐไธญไธ่ƒฝไฝฟ็”จ `[name]` ๅฝขๅผใ€‚' PARSER_UNKNOWN_ATTRIBUTE = 'ๅฑ€้ƒจๅ˜้‡ๅฑžๆ€งๅบ”่ฏฅๆ˜ฏ `const` ๆˆ– `close`' +PARSER_AMBIGUOUS_SYNTAX = +'ๅœจ Lua 5.1 ไธญ๏ผŒๅ‡ฝๆ•ฐ่ฐƒ็”จ็š„ๅทฆๆ‹ฌๅทๅฟ…้กปไธŽๅ‡ฝๆ•ฐๅœจๅŒไธ€่กŒใ€‚' +PARSER_NEED_PAREN = +'้œ€่ฆๆทปๅŠ ไธ€ๅฏนๆ‹ฌๅทใ€‚' +PARSER_NESTING_LONG_MARK = +'Lua 5.1 ไธญไธๅ…่ฎธไฝฟ็”จๅตŒๅฅ—็š„ `[[...]]` ใ€‚' +PARSER_LOCAL_LIMIT = +'ๅช่ƒฝๅŒๆ—ถๅญ˜ๅœจ200ไธชๆดป่ทƒ็š„ๅฑ€้ƒจๅ˜้‡ไธŽไธŠๅ€ผใ€‚' PARSER_LUADOC_MISS_CLASS_NAME = '็ผบๅฐ‘็ฑปๅ็งฐใ€‚' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = @@ -412,8 +452,12 @@ ACTION_DISABLE_DIAG_FILE= 'ๅœจๆญคๆ–‡ไปถ็ฆ็”จ่ฏŠๆ–ญ ({})ใ€‚' ACTION_MARK_ASYNC = 'ๅฐ†ๅฝ“ๅ‰ๅ‡ฝๆ•ฐๆ ‡่ฎฐไธบๅผ‚ๆญฅใ€‚' -ACTION_ADD_DICT = -- TODO: need translate! -'Add \'{}\' to workspace dict' +ACTION_ADD_DICT = +'ๅฐ† \'{}\' ๆทปๅŠ ๅˆฐๅทฅไฝœๅŒบ็š„่ฏๅ…ธไธญใ€‚' +ACTION_FIX_ADD_PAREN = +'ๆทปๅŠ ๆ‹ฌๅทใ€‚' +ACTION_AUTOREQUIRE = +"ๅฏผๅ…ฅ '{}' ไฝœไธบ `{}`" COMMAND_DISABLE_DIAG = '็ฆ็”จ่ฏŠๆ–ญ' @@ -433,8 +477,10 @@ COMMAND_JSON_TO_LUA = 'JSON ่ฝฌ Lua' COMMAND_JSON_TO_LUA_FAILED = 'JSON ่ฝฌ Lua ๅคฑ่ดฅ๏ผš{}' -COMMAND_ADD_DICT = -- TODO: need translate! -'Add Word to dictionary' +COMMAND_ADD_DICT = +'ๅฐ†ๅ•่ฏๆทปๅŠ ๅˆฐๅญ—ๅ…ธ' +COMMAND_REFERENCE_COUNT = +'{} ไธชๅผ•็”จ' COMPLETION_IMPORT_FROM = 'ไปŽ {} ไธญๅฏผๅ…ฅ' @@ -512,7 +558,7 @@ WINDOW_APPLY_SETTING = WINDOW_CHECK_SEMANTIC = 'ๅฆ‚ๆžœไฝ ๆญฃๅœจไฝฟ็”จๅธ‚ๅœบไธญ็š„้ขœ่‰ฒไธป้ข˜๏ผŒไฝ ๅฏ่ƒฝ้œ€่ฆๅŒๆ—ถไฟฎๆ”น `editor.semanticHighlighting.enabled` ้€‰้กนไธบ `true` ๆ‰ไผšไฝฟ่ฏญไน‰็€่‰ฒ็”Ÿๆ•ˆใ€‚' WINDOW_TELEMETRY_HINT = -'่ฏทๅ…่ฎธๅ‘้€ๅŒฟๅ็š„ไฝฟ็”จๆ•ฐๆฎไธŽ้”™่ฏฏๆŠฅๅ‘Š๏ผŒๅธฎๅŠฉๆˆ‘ไปฌ่ฟ›ไธ€ๆญฅๅฎŒๅ–„ๆญคๆ’ไปถใ€‚ๅœจ[ๆญคๅค„](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้˜…่ฏปๆˆ‘ไปฌ็š„้š็งๅฃฐๆ˜Žใ€‚' +'่ฏทๅ…่ฎธๅ‘้€ๅŒฟๅ็š„ไฝฟ็”จๆ•ฐๆฎไธŽ้”™่ฏฏๆŠฅๅ‘Š๏ผŒๅธฎๅŠฉๆˆ‘ไปฌ่ฟ›ไธ€ๆญฅๅฎŒๅ–„ๆญคๆ’ไปถใ€‚ๅœจ[ๆญคๅค„](https://luals.github.io/privacy/#language-server)้˜…่ฏปๆˆ‘ไปฌ็š„้š็งๅฃฐๆ˜Žใ€‚' WINDOW_TELEMETRY_ENABLE = 'ๅ…่ฎธ' WINDOW_TELEMETRY_DISABLE = @@ -522,7 +568,7 @@ WINDOW_CLIENT_NOT_SUPPORT_CONFIG = WINDOW_LCONFIG_NOT_SUPPORT_CONFIG= 'ๆš‚ไธๆ”ฏๆŒ่‡ชๅŠจไฟฎๆ”นๆœฌๅœฐ่ฎพ็ฝฎ๏ผŒ่ฏทๆ‰‹ๅŠจไฟฎๆ”นๅฆ‚ไธ‹่ฎพ็ฝฎ๏ผš' WINDOW_MANUAL_CONFIG_ADD = -'ไธบ `{key}` ๆทปๅŠ ๅ€ผ `{value:q}`;' +'ไธบ `{key}` ๆทปๅŠ ๅ…ƒ็ด  `{value:q}`;' WINDOW_MANUAL_CONFIG_SET = 'ๅฐ† `{key}` ็š„ๅ€ผ่ฎพ็ฝฎไธบ `{value:q}`;' WINDOW_MANUAL_CONFIG_PROP = @@ -535,6 +581,14 @@ WINDOW_ASK_APPLY_LIBRARY = 'ๆ˜ฏๅฆ้œ€่ฆๅฐ†ไฝ ็š„ๅทฅไฝœ็Žฏๅขƒ้…็ฝฎไธบ `{}` ๏ผŸ' WINDOW_SEARCHING_IN_FILES = 'ๆญฃๅœจๆ–‡ไปถไธญๆœ็ดข...' +WINDOW_CONFIG_LUA_DEPRECATED = +'`config.lua` ๅทฒๅบŸๅผƒ๏ผŒ่ฏทๆ”น็”จ `config.json` ใ€‚' +WINDOW_CONVERT_CONFIG_LUA = +'่ฝฌๆขไธบ `config.json`' +WINDOW_MODIFY_REQUIRE_PATH = +'ไฝ ๆƒณ่ฆไฟฎๆ”น `require` ็š„่ทฏๅพ„ๅ—๏ผŸ' +WINDOW_MODIFY_REQUIRE_OK = +'ไฟฎๆ”น' CONFIG_LOAD_FAILED = 'ๆ— ๆณ•่ฏปๅ–่ฎพ็ฝฎๆ–‡ไปถ๏ผš{}' @@ -542,6 +596,24 @@ CONFIG_LOAD_ERROR = '่ฎพ็ฝฎๆ–‡ไปถๅŠ ่ฝฝ้”™่ฏฏ๏ผš{}' CONFIG_TYPE_ERROR = '่ฎพ็ฝฎๆ–‡ไปถๅฟ…้กปๆ˜ฏluaๆˆ–jsonๆ ผๅผ๏ผš{}' +CONFIG_MODIFY_FAIL_SYNTAX_ERROR = +'ไฟฎๆ”น่ฎพ็ฝฎๅคฑ่ดฅ๏ผŒ่ฎพ็ฝฎๆ–‡ไปถไธญๆœ‰่ฏญๆณ•้”™่ฏฏ๏ผš{}' +CONFIG_MODIFY_FAIL_NO_WORKSPACE = +[[ +ไฟฎๆ”น่ฎพ็ฝฎๅคฑ่ดฅ๏ผš +* ๅฝ“ๅ‰ๆจกๅผไธบๅ•ๆ–‡ไปถๆจกๅผ๏ผŒๆœๅŠกๅ™จๅช่ƒฝๅœจๅทฅไฝœๅŒบไธญๅˆ›ๅปบ `.luarc.json` ๆ–‡ไปถใ€‚ +* ่ฏญ่จ€ๅฎขๆˆท็ซฏไธๆ”ฏๆŒไปŽๆœๅŠกๅ™จไพงไฟฎๆ”น่ฎพ็ฝฎใ€‚ + +่ฏทๆ‰‹ๅŠจไฟฎๆ”นไปฅไธ‹่ฎพ็ฝฎ๏ผš +{} +]] +CONFIG_MODIFY_FAIL = +[[ +ไฟฎๆ”น่ฎพ็ฝฎๅคฑ่ดฅ + +่ฏทๆ‰‹ๅŠจไฟฎๆ”นไปฅไธ‹่ฎพ็ฝฎ๏ผš +{} +]] PLUGIN_RUNTIME_ERROR = [[ @@ -576,6 +648,49 @@ CLI_CHECK_SUCCESS = '่ฏŠๆ–ญๅฎŒๆˆ๏ผŒๆฒกๆœ‰ๅ‘็Žฐ้—ฎ้ข˜' CLI_CHECK_RESULTS = '่ฏŠๆ–ญๅฎŒๆˆ๏ผŒๅ…ฑๆœ‰ {} ไธช้—ฎ้ข˜๏ผŒ่ฏทๆŸฅ็œ‹ {}' +CLI_DOC_INITING = +'ๅŠ ่ฝฝๆ–‡ๆกฃ ...' +CLI_DOC_DONE = +[[ +ๆ–‡ๆกฃๅฏผๅ‡บๅฎŒๆˆ๏ผ +ๅŽŸๅง‹ๆ•ฐๆฎ: {} +Markdown(ๆผ”็คบ็”จ): {} +]] + +TYPE_ERROR_ENUM_GLOBAL_DISMATCH = +'็ฑปๅž‹ `{child}` ๆ— ๆณ•ๅŒน้… `{parent}` ็š„ๆžšไธพ็ฑปๅž‹' +TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED = +'ๆ— ๆณ•ๅœจๆžšไธพไธญไฝฟ็”จๆณ›ๅž‹ `{child}`' +TYPE_ERROR_ENUM_LITERAL_DISMATCH = +'ๅญ—้ข้‡ `{child}` ๆ— ๆณ•ๅŒน้… `{parent}` ็š„ๆžšไธพๅ€ผ' +TYPE_ERROR_ENUM_OBJECT_DISMATCH = +'ๅฏน่ฑก `{child}` ๆ— ๆณ•ๅŒน้… `{parent}` ็š„ๆžšไธพๅ€ผ๏ผŒๅฎƒไปฌๅฟ…้กปๆ˜ฏๅŒไธ€ไธชๅฏน่ฑก' +TYPE_ERROR_ENUM_NO_OBJECT = +'ๆ— ๆณ•่ฏ†ๅˆซไผ ๅ…ฅ็š„ๆžšไธพๅ€ผ `{child}`' +TYPE_ERROR_INTEGER_DISMATCH = +'ๅญ—้ข้‡ `{child}` ๆ— ๆณ•ๅŒน้…ๆ•ดๆ•ฐ `{parent}`' +TYPE_ERROR_STRING_DISMATCH = +'ๅญ—้ข้‡ `{child}` ๆ— ๆณ•ๅŒน้…ๅญ—็ฌฆไธฒ `{parent}`' +TYPE_ERROR_BOOLEAN_DISMATCH = +'ๅญ—้ข้‡ `{child}` ๆ— ๆณ•ๅŒน้…ๅธƒๅฐ”ๅ€ผ `{parent}`' +TYPE_ERROR_TABLE_NO_FIELD = +'่กจไธญไธๅญ˜ๅœจๅญ—ๆฎต `{key}`' +TYPE_ERROR_TABLE_FIELD_DISMATCH = +'ๅญ—ๆฎต `{key}` ็š„็ฑปๅž‹ไธบ `{child}`๏ผŒๆ— ๆณ•ๅŒน้… `{parent}`' +TYPE_ERROR_CHILD_ALL_DISMATCH = +'`{child}` ไธญ็š„ๆ‰€ๆœ‰ๅญ็ฑปๅž‹ๅ‡ๆ— ๆณ•ๅŒน้… `{parent}`' +TYPE_ERROR_PARENT_ALL_DISMATCH = +'`{child}` ๆ— ๆณ•ๅŒน้… `{parent}` ไธญ็š„ไปปไฝ•ๅญ็ฑป' +TYPE_ERROR_UNION_DISMATCH = +'`{child}` ๆ— ๆณ•ๅŒน้… `{parent}`' +TYPE_ERROR_OPTIONAL_DISMATCH = +'ๅฏ้€‰็ฑปๅž‹ๆ— ๆณ•ๅŒน้… `{parent}`' +TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = +'ๆ— ๆณ•ๅฐ†ๆ•ฐๅญ— `{child}` ่ฝฌๆขไธบๆ•ดๆ•ฐ' +TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = +'ๆ— ๆณ•ๅฐ†ๆ•ฐๅญ—็ฑปๅž‹่ฝฌๆขไธบๆ•ดๆ•ฐ็ฑปๅž‹' +TYPE_ERROR_DISMATCH = +'็ฑปๅž‹ `{child}` ๆ— ๆณ•ๅŒน้… `{parent}`' LUADOC_DESC_CLASS = -- TODO: need translate! [=[ @@ -588,7 +703,7 @@ Defines a class/table structure Manager = {} ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#class) +[View Wiki](https://luals.github.io/wiki/annotations#class) ]=] LUADOC_DESC_TYPE = -- TODO: need translate! [=[ @@ -639,7 +754,7 @@ local x --x[""] is true local myFunction ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#types-and-type) +[View Wiki](https://luals.github.io/wiki/annotations#type) ]=] LUADOC_DESC_ALIAS = -- TODO: need translate! [=[ @@ -674,8 +789,22 @@ function find(path, pattern) end ---@param style font-style Style to apply function setFontStyle(style) end ``` + +### Literal Enum +``` +local enums = { + READ = 0, + WRITE = 1, + CLOSED = 2 +} + +---@alias FileStates +---| `enums.READ` +---| `enums.WRITE` +---| `enums.CLOSE` +``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +[View Wiki](https://luals.github.io/wiki/annotations#alias) ]=] LUADOC_DESC_PARAM = -- TODO: need translate! [=[ @@ -700,7 +829,7 @@ function get(url, headers, timeout) end function concat(base, ...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#param) +[View Wiki](https://luals.github.io/wiki/annotations#param) ]=] LUADOC_DESC_RETURN = -- TODO: need translate! [=[ @@ -738,12 +867,13 @@ function getFirstLast() end function getTags(item) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#return) +[View Wiki](https://luals.github.io/wiki/annotations#return) ]=] LUADOC_DESC_FIELD = -- TODO: need translate! [=[ Declare a field in a class/table. This allows you to provide more in-depth -documentation for a table. +documentation for a table. As of `v3.6.0`, you can mark a field as `private`, +`protected`, `public`, or `package`. ## Syntax `---@field [description]` @@ -768,7 +898,7 @@ response = get("localhost") statusCode = response.status.code ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#field) +[View Wiki](https://luals.github.io/wiki/annotations#field) ]=] LUADOC_DESC_GENERIC = -- TODO: need translate! [=[ @@ -825,7 +955,7 @@ local v = Generic("Foo") -- v is an object of Foo -- we give for key (K) or value (V) ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#generics-and-generic) +[View Wiki](https://luals.github.io/wiki/annotations/#generic) ]=] LUADOC_DESC_VARARG = -- TODO: need translate! [=[ @@ -844,7 +974,7 @@ provide typing or allow descriptions. function concat(...) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#vararg) +[View Wiki](https://luals.github.io/wiki/annotations#vararg) ]=] LUADOC_DESC_OVERLOAD = -- TODO: need translate! [=[ @@ -859,7 +989,7 @@ Allows defining of multiple function signatures. function table.insert(t, position, value) end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#overload) +[View Wiki](https://luals.github.io/wiki/annotations#overload) ]=] LUADOC_DESC_DEPRECATED = -- TODO: need translate! [=[ @@ -870,7 +1000,7 @@ being ~~struck through~~. `---@deprecated` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#deprecated) +[View Wiki](https://luals.github.io/wiki/annotations#deprecated) ]=] LUADOC_DESC_META = -- TODO: need translate! [=[ @@ -885,7 +1015,7 @@ There are 3 main distinctions to note with meta files: `---@meta` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#meta) +[View Wiki](https://luals.github.io/wiki/annotations#meta) ]=] LUADOC_DESC_VERSION = -- TODO: need translate! [=[ @@ -910,7 +1040,7 @@ function onlyWorksInJIT() end function oldLuaOnly() end ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#version) +[View Wiki](https://luals.github.io/wiki/annotations#version) ]=] LUADOC_DESC_SEE = -- TODO: need translate! [=[ @@ -920,7 +1050,7 @@ Define something that can be viewed for more information `---@see ` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#see) +[View Wiki](https://luals.github.io/wiki/annotations#see) ]=] LUADOC_DESC_DIAGNOSTIC = -- TODO: need translate! [=[ @@ -928,7 +1058,7 @@ Enable/disable diagnostics for error/warnings/etc. Actions: `disable`, `enable`, `disable-line`, `disable-next-line` -[Names](https://github.com/sumneko/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) +[Names](https://github.com/LuaLS/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) ## Syntax `---@diagnostic [: ]` @@ -946,7 +1076,7 @@ local unused = "hello world" ---@diagnostic enable: unused-local ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#diagnostic) +[View Wiki](https://luals.github.io/wiki/annotations#diagnostic) ]=] LUADOC_DESC_MODULE = -- TODO: need translate! [=[ @@ -963,7 +1093,7 @@ local stringUtils local module = require('string.utils') ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#module) +[View Wiki](https://luals.github.io/wiki/annotations#module) ]=] LUADOC_DESC_ASYNC = -- TODO: need translate! [=[ @@ -973,7 +1103,7 @@ Marks a function as asynchronous. `---@async` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#async) +[View Wiki](https://luals.github.io/wiki/annotations#async) ]=] LUADOC_DESC_NODISCARD = -- TODO: need translate! [=[ @@ -985,7 +1115,7 @@ be ignored. `---@nodiscard` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#nodiscard) +[View Wiki](https://luals.github.io/wiki/annotations#nodiscard) ]=] LUADOC_DESC_CAST = -- TODO: need translate! [=[ @@ -1020,7 +1150,7 @@ local x --> string|table print(x) --> table ``` --- -[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#cast) +[View Wiki](https://luals.github.io/wiki/annotations#cast) ]=] LUADOC_DESC_OPERATOR = -- TODO: need translate! [=[ @@ -1050,12 +1180,12 @@ pA = Passcode.new(1234) pB = -pA --> integer ``` -[View Request](https://github.com/sumneko/lua-language-server/issues/599) +[View Request](https://github.com/LuaLS/lua-language-server/issues/599) ]=] LUADOC_DESC_ENUM = -- TODO: need translate! [=[ Mark a table as an enum. If you want an enum but can't define it as a Lua -table, take a look at the [`@alias`](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +table, take a look at the [`@alias`](https://luals.github.io/wiki/annotations#alias) tag. ## Syntax @@ -1079,3 +1209,103 @@ local function setColor(color) end setColor(colors.green) ``` ]=] +LUADOC_DESC_SOURCE = -- TODO: need translate! +[=[ +Provide a reference to some source code which lives in another file. When +searching for the defintion of an item, its `@source` will be used. + +## Syntax +`@source ` + +## Usage +``` +---You can use absolute paths +---@source C:/Users/me/Documents/program/myFile.c +local a + +---Or URIs +---@source file:///C:/Users/me/Documents/program/myFile.c:10 +local b + +---Or relative paths +---@source local/file.c +local c + +---You can also include line and char numbers +---@source local/file.c:10:8 +local d +``` +]=] +LUADOC_DESC_PACKAGE = -- TODO: need translate! +[=[ +Mark a function as private to the file it is defined in. A packaged function +cannot be accessed from another file. + +## Syntax +`@package` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@package +---This cannot be accessed in another file +function Animal:eyesCount() + return self.eyes +end +``` +]=] +LUADOC_DESC_PRIVATE = -- TODO: need translate! +[=[ +Mark a function as private to a @class. Private functions can be accessed only +from within their class and are not accessable from child classes. + +## Syntax +`@private` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@private +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---NOT PERMITTED! +myDog:eyesCount(); +``` +]=] +LUADOC_DESC_PROTECTED = -- TODO: need translate! +[=[ +Mark a function as protected within a @class. Protected functions can be +accessed only from within their class or from child classes. + +## Syntax +`@protected` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@protected +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---Permitted because function is protected, not private. +myDog:eyesCount(); +``` +]=] diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua index baa3a92f8..78e7fb685 100644 --- a/locale/zh-cn/setting.lua +++ b/locale/zh-cn/setting.lua @@ -1,5 +1,7 @@ ---@diagnostic disable: undefined-global +config.addonManager.enable = +"ๆ˜ฏๅฆๅฏ็”จๆ‰ฉๅฑ•็š„้™„ๅŠ ๆ’ไปถ็ฎก็†ๅ™จ(Addon Manager)" config.runtime.version = "Lua่ฟ่กŒ็‰ˆๆœฌใ€‚" config.runtime.path = @@ -25,7 +27,7 @@ config.runtime.unicodeName = config.runtime.nonstandardSymbol = "ๆ”ฏๆŒ้žๆ ‡ๅ‡†็š„็ฌฆๅทใ€‚่ฏทๅŠกๅฟ…็กฎ่ฎคไฝ ็š„่ฟ่กŒ็Žฏๅขƒๆ”ฏๆŒ่ฟ™ไบ›็ฌฆๅทใ€‚" config.runtime.plugin = -"ๆ’ไปถ่ทฏๅพ„๏ผŒ่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/Plugins)ไบ†่งฃ็”จๆณ•ใ€‚" +"ๆ’ไปถ่ทฏๅพ„๏ผŒ่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://luals.github.io/wiki/plugins)ไบ†่งฃ็”จๆณ•ใ€‚" config.runtime.pluginArgs = -- TODO: need translate! "Additional arguments for the plugin." config.runtime.fileEncoding = @@ -76,8 +78,16 @@ config.diagnostics.groupFileStatus = ่ฎพ็ฝฎไธบ `Fallback` ๆ„ๅ‘ณ็€็ป„ไธญ็š„่ฏŠๆ–ญ็”ฑ `diagnostics.neededFileStatus` ๅ•็‹ฌ่ฎพ็ฝฎใ€‚ ๅ…ถไป–่ฎพ็ฝฎๅฐ†่ฆ†็›–ๅ•็‹ฌ่ฎพ็ฝฎ๏ผŒไฝ†ๆ˜ฏไธไผš่ฆ†็›–ไปฅ `!` ็ป“ๅฐพ็š„่ฎพ็ฝฎใ€‚ ]] +config.diagnostics.workspaceEvent = +"่ฎพ็ฝฎ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๆ—ถๆœบใ€‚" +config.diagnostics.workspaceEvent.OnChange = +"ๅฝ“ๆ–‡ไปถๅ‘็”Ÿๅ˜ๅŒ–ๆ—ถ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚" +config.diagnostics.workspaceEvent.OnSave = +"ๅฝ“ๆ–‡ไปถไฟๅญ˜ๆ—ถ่งฆๅ‘ๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚" +config.diagnostics.workspaceEvent.None = +"ๅ…ณ้—ญๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚" config.diagnostics.workspaceDelay = -"่ฟ›่กŒๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๅปถ่ฟŸ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ๅฝ“ไฝ ๅฏๅŠจๅทฅไฝœๅŒบ๏ผŒๆˆ–็ผ–่พ‘ไบ†ไปปๆ„ๆ–‡ไปถๅŽ๏ผŒๅฐ†ไผšๅœจๅŽๅฐๅฏนๆ•ดไธชๅทฅไฝœๅŒบ่ฟ›่กŒ้‡ๆ–ฐ่ฏŠๆ–ญใ€‚่ฎพ็ฝฎไธบ่ดŸๆ•ฐๅฏไปฅ็ฆ็”จๅทฅไฝœๅŒบ่ฏŠๆ–ญใ€‚" +"่ฟ›่กŒๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„ๅปถ่ฟŸ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚" config.diagnostics.workspaceRate = "ๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„่ฟ่กŒ้€Ÿ็އ๏ผˆ็™พๅˆ†ๆฏ”๏ผ‰ใ€‚้™ไฝŽ่ฏฅๅ€ผไผšๅ‡ๅฐ‘CPUๅ ็”จ๏ผŒไฝ†ๆ˜ฏไนŸไผš้™ไฝŽๅทฅไฝœๅŒบ่ฏŠๆ–ญ็š„้€Ÿๅบฆใ€‚ไฝ ๅฝ“ๅ‰ๆญฃๅœจ็ผ–่พ‘็š„ๆ–‡ไปถ็š„่ฏŠๆ–ญๆ€ปๆ˜ฏๅ…จ้€ŸๅฎŒๆˆ๏ผŒไธๅ—่ฏฅ้€‰้กนๅฝฑๅ“ใ€‚" config.diagnostics.libraryFiles = @@ -124,7 +134,7 @@ config.workspace.checkThirdParty = * Jass ]] config.workspace.userThirdParty = -'ๅœจ่ฟ™้‡ŒๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบ“้€‚้…ๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏทๅ‚่€ƒๅ†…็ฝฎ็š„[้…็ฝฎๆ–‡ไปถ่ทฏๅพ„](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd)' +'ๅœจ่ฟ™้‡ŒๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบ“้€‚้…ๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏทๅ‚่€ƒๅ†…็ฝฎ็š„[้…็ฝฎๆ–‡ไปถ่ทฏๅพ„](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd)' config.workspace.supportScheme = 'ไธบไปฅไธ‹ scheme ็š„luaๆ–‡ไปถๆไพ›่ฏญ่จ€ๆœๅŠกใ€‚' config.completion.enable = @@ -247,6 +257,8 @@ config.hint.semicolon.SameLine = '2ไธช่ฏญๅฅๅœจๅŒไธ€่กŒๆ—ถ๏ผŒๅœจๅฎƒไปฌไน‹้—ดๆ˜พ็คบๅˆ†ๅทใ€‚' config.hint.semicolon.Disable = '็ฆ็”จ่™šๆ‹Ÿๅˆ†ๅทใ€‚' +config.codeLens.enable = +'ๅฏ็”จไปฃ็ ๅบฆ้‡ใ€‚' config.format.enable = 'ๅฏ็”จไปฃ็ ๆ ผๅผๅŒ–็จ‹ๅบใ€‚' config.format.defaultConfig = @@ -256,20 +268,16 @@ config.format.defaultConfig = ]] config.spell.dict = 'ๆ‹ผๅ†™ๆฃ€ๆŸฅ็š„่‡ชๅฎšไน‰ๅ•่ฏใ€‚' +config.nameStyle.config = +'่ฎพๅฎšๅ‘ฝๅ้ฃŽๆ ผๆฃ€ๆŸฅ็š„้…็ฝฎ' config.telemetry.enable = [[ -ๅฏ็”จ้ฅๆต‹๏ผŒ้€š่ฟ‡็ฝ‘็ปœๅ‘้€ไฝ ็š„็ผ–่พ‘ๅ™จไฟกๆฏไธŽ้”™่ฏฏๆ—ฅๅฟ—ใ€‚ๅœจ[ๆญคๅค„](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้˜…่ฏปๆˆ‘ไปฌ็š„้š็งๅฃฐๆ˜Žใ€‚ +ๅฏ็”จ้ฅๆต‹๏ผŒ้€š่ฟ‡็ฝ‘็ปœๅ‘้€ไฝ ็š„็ผ–่พ‘ๅ™จไฟกๆฏไธŽ้”™่ฏฏๆ—ฅๅฟ—ใ€‚ๅœจ[ๆญคๅค„](https://luals.github.io/privacy/#language-server)้˜…่ฏปๆˆ‘ไปฌ็š„้š็งๅฃฐๆ˜Žใ€‚ ]] config.misc.parameters = -'VSCodeไธญๅฏๅŠจ่ฏญ่จ€ๆœๅŠกๆ—ถ็š„[ๅ‘ฝไปค่กŒๅ‚ๆ•ฐ](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)ใ€‚' -config.IntelliSense.traceLocalSet = -'่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)ไบ†่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceReturn = -'่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)ไบ†่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceBeSetted = -'่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)ไบ†่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceFieldInject = -'่ฏทๆŸฅ้˜…[ๆ–‡ๆกฃ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)ไบ†่งฃ็”จๆณ•ใ€‚' +'VSCodeไธญๅฏๅŠจ่ฏญ่จ€ๆœๅŠกๆ—ถ็š„[ๅ‘ฝไปค่กŒๅ‚ๆ•ฐ](https://luals.github.io/wiki/usage#arguments)ใ€‚' +config.misc.executablePath = +'VSCodeไธญๆŒ‡ๅฎšๅฏๆ‰ง่กŒๆ–‡ไปถ่ทฏๅพ„ใ€‚' config.type.castNumberToInteger = 'ๅ…่ฎธๅฐ† `number` ็ฑปๅž‹่ต‹็ป™ `integer` ็ฑปๅž‹ใ€‚' config.type.weakUnionCheck = @@ -284,6 +292,18 @@ config.type.weakNilCheck = ๆญค่ฎพ็ฝฎไธบ `false` ๆ—ถ๏ผŒ`numer|nil` ็ฑปๅž‹ๆ— ๆณ•่ต‹็ป™ `number` ็ฑปๅž‹๏ผ›ไธบ `true` ๆ˜ฏๅˆ™ๅฏไปฅใ€‚ ]] +config.type.inferParamType = +[[ +ๆœชๆณจ้‡Šๅ‚ๆ•ฐ็ฑปๅž‹ๆ—ถ๏ผŒๅ‚ๆ•ฐ็ฑปๅž‹็”ฑๅ‡ฝๆ•ฐไผ ๅ…ฅๅ‚ๆ•ฐๆŽจๆ–ญใ€‚ + +ๅฆ‚ๆžœ่ฎพ็ฝฎไธบ "false"๏ผŒๅˆ™ๅœจๆœชๆณจ้‡Šๆ—ถ๏ผŒๅ‚ๆ•ฐ็ฑปๅž‹ไธบ "any"ใ€‚ +]] +config.doc.privateName = +'ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบ็งๆœ‰๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๆ˜ฏ็งๆœ‰ๅญ—ๆฎต๏ผŒๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„็ฑปไธญ่ฎฟ้—ฎใ€‚' +config.doc.protectedName = +'ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบๅ—ไฟๆŠค๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๆ˜ฏๅ—ไฟๆŠค็š„ๅญ—ๆฎต๏ผŒๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„็ฑปๆžๅ…ถๅญ็ฑปไธญ่ฎฟ้—ฎใ€‚' +config.doc.packageName = +'ๅฐ†็‰นๅฎšๅ็งฐ็š„ๅญ—ๆฎต่ง†ไธบpackage๏ผŒไพ‹ๅฆ‚ `m_*` ๆ„ๅ‘ณ็€ `XXX.m_id` ไธŽ `XXX.m_type` ๅช่ƒฝๅœจๅฎšไน‰ๆ‰€ๅœจ็š„ๆ–‡ไปถไธญ่ฎฟ้—ฎใ€‚' config.diagnostics['unused-local'] = 'ๆœชไฝฟ็”จ็š„ๅฑ€้ƒจๅ˜้‡' config.diagnostics['unused-function'] = @@ -318,3 +338,104 @@ config.diagnostics['empty-block'] = '็ฉบไปฃ็ ๅ—' config.diagnostics['redundant-value'] = '่ต‹ๅ€ผๆ“ไฝœๆ—ถ๏ผŒๅ€ผ็š„ๆ•ฐ้‡ๆฏ”่ขซ่ต‹ๅ€ผ็š„ๅฏน่ฑกๅคš' +config.diagnostics['assign-type-mismatch'] = +'ๅ€ผ็ฑปๅž‹ไธŽ่ต‹ๅ€ผๅ˜้‡็ฑปๅž‹ไธๅŒน้…' +config.diagnostics['await-in-sync'] = +'ๅŒๆญฅๅ‡ฝๆ•ฐไธญๅผ‚ๆญฅๅ‡ฝๆ•ฐ่ฐƒ็”จ' +config.diagnostics['cast-local-type'] = +'ๅทฒๆ˜พๅผๅฎšไน‰ๅ˜้‡็ฑปๅž‹ไธŽ่ฆๅฎšไน‰็š„ๅ€ผ็š„็ฑปๅž‹ไธๅŒน้…' +config.diagnostics['cast-type-mismatch'] = +'ๅ˜้‡่ขซ่ฝฌๆขไธบไธŽๅ…ถๅˆๅง‹็ฑปๅž‹ไธๅŒน้…็š„็ฑปๅž‹' +config.diagnostics['circular-doc-class'] = +'ไธคไธช็ฑป็›ธไบ’็ปงๆ‰ฟๅนถไบ’็›ธๅพช็Žฏ' +config.diagnostics['close-non-object'] = -- TODO: need translate! +'Enable diagnostics for attempts to close a variable with a non-object.' +config.diagnostics['code-after-break'] = +'ๆ”พๅœจๅพช็Žฏไธญbreak่ฏญๅฅๅŽ้ข็š„ไปฃ็ ' +config.diagnostics['codestyle-check'] = -- TODO: need translate! +'Enable diagnostics for incorrectly styled lines.' +config.diagnostics['count-down-loop'] = +'forๅพช็Žฏๆฐธ่ฟœๆ— ๆณ•่พพๅˆฐๆœ€ๅคง/ๆž้™ๅ€ผ(ๅœจ้€’ๅ‡ๆ—ถ้€’ๅขž)' +config.diagnostics['deprecated'] = +'ๅ˜้‡ๅทฒ่ขซๆ ‡่ฎฐไธบdeprecated(่ฟ‡ๆ—ถ)ไฝ†ไปๅœจไฝฟ็”จ' +config.diagnostics['different-requires'] = +'required็š„ๅŒไธ€ไธชๆ–‡ไปถไฝฟ็”จไบ†ไธคไธชไธๅŒ็š„ๅๅญ—' +config.diagnostics['discard-returns'] = +'ๅ‡ฝๆ•ฐ็š„่ฟ”ๅ›žๅ€ผ่ขซๅฟฝ็•ฅ(ๅ‡ฝๆ•ฐ่ขซ`@nodiscard`ๆ ‡่ฎฐๆ—ถ)' +config.diagnostics['doc-field-no-class'] = +'ไธบไธๅญ˜ๅœจ็š„็ฑป`@class`ๆ ‡่ฎฐ`@field`ๅญ—ๆฎต' +config.diagnostics['duplicate-doc-alias'] = +'`@alias`ๅญ—ๆฎต็š„ๅๅญ—ๅ†ฒ็ช' +config.diagnostics['duplicate-doc-field'] = +'`@field`ๅญ—ๆฎต็š„ๅๅญ—ๅ†ฒ็ช' +config.diagnostics['duplicate-doc-param'] = +'`@param`ๅญ—ๆฎต็š„ๅๅญ—ๅ†ฒ็ช' +config.diagnostics['duplicate-set-field'] = +'ๅœจไธ€ไธช็ฑปไธญๅคšๆฌกๅฎšไน‰ๅŒไธ€ๅญ—ๆฎต' +config.diagnostics['incomplete-signature-doc'] = +'`@param`ๆˆ–`@return`็š„ๆณจ้‡ŠไธๅฎŒๆ•ด' +config.diagnostics['invisible'] = +'ไฝฟ็”จไธๅฏ่ง็š„ๅ€ผ' +config.diagnostics['missing-global-doc'] = +'ๅ…จๅฑ€ๅ˜้‡็š„ๆณจ้‡Š็ผบๅคฑ(ๅ…จๅฑ€ๅ‡ฝๆ•ฐๅฟ…้กปไธบๆ‰€ๆœ‰ๅ‚ๆ•ฐๅ’Œ่ฟ”ๅ›žๅ€ผๆไพ›ๆณจ้‡Šๅ’Œๆณจ้‡Š)' +config.diagnostics['missing-local-export-doc'] = +'ๅฏผๅ‡บ็š„ๆœฌๅœฐๅ‡ฝๆ•ฐ็ผบๅฐ‘ๆณจ้‡Š(ๅฏผๅ‡บ็š„ๆœฌๅœฐๅ‡ฝๆ•ฐๅฟ…้กปๆœ‰ๅŒ…ๆ‹ฌๆœฌ่บซไปฅๅŠๆ‰€ๆœ‰ๅ‚ๆ•ฐๅ’Œ่ฟ”ๅ›žๅ€ผ็š„ๆณจ้‡Š)' +config.diagnostics['missing-parameter'] = +'ๅ‡ฝๆ•ฐๅ‚ๆ•ฐๆ•ฐๅฐ‘ไบŽๆณจ้‡Šๅ‡ฝๆ•ฐๅ‚ๆ•ฐๆ•ฐ' +config.diagnostics['missing-return'] = +'ๅ‡ฝๆ•ฐๅธฆๆœ‰่ฟ”ๅ›žๆณจ้‡Š่€Œๆ— ่ฟ”ๅ›ž่ฏญๅฅ' +config.diagnostics['missing-return-value'] = +'ๅ‡ฝๆ•ฐๆ— ๅ€ผ่ฟ”ๅ›žไฝ†ๅ‡ฝๆ•ฐไฝฟ็”จ`@return`ๆ ‡่ฎฐไบ†่ฟ”ๅ›žๅ€ผ' +config.diagnostics['need-check-nil'] = +'ๅ˜้‡ไน‹ๅ‰่ขซ่ต‹ๅ€ผไธบ`nil`ๆˆ–ๅฏ้€‰ๅ€ผ(ๅฏ่ƒฝไธบ `nil`)' +config.diagnostics['no-unknown'] = +'ๅ˜้‡็š„ๆœช็Ÿฅ็ฑปๅž‹ๆ— ๆณ•ๆŽจๆ–ญ' +config.diagnostics['not-yieldable'] = +'ไธๅ…่ฎธ่ฐƒ็”จ `coroutine.yield()` ' +config.diagnostics['param-type-mismatch'] = +'็ป™ๅฎšๅ‚ๆ•ฐ็š„็ฑปๅž‹ไธŽๅ‡ฝๆ•ฐๅฎšไน‰ๆ‰€่ฆๆฑ‚็š„็ฑปๅž‹(`@param`)ไธๅŒน้…' +config.diagnostics['redundant-return'] = +'ๅฝ“ๆ”พ็ฝฎไธ€ไธชไธ้œ€่ฆ็š„่ฟ”ๅ›žๅ€ผๆ—ถ่งฆๅ‘(ๅ‡ฝๆ•ฐไผš่‡ช่กŒ้€€ๅ‡บ)' +config.diagnostics['redundant-return-value']= +'่ฟ”ๅ›ž`@return`ๆณจ้‡ŠๆœชๆŒ‡ๅฎš็š„้ขๅค–ๅ€ผ' +config.diagnostics['return-type-mismatch'] = +'่ฟ”ๅ›žๅ€ผ็š„็ฑปๅž‹ไธŽ`@return`ไธญๅฃฐๆ˜Ž็š„็ฑปๅž‹ไธๅŒน้…' +config.diagnostics['spell-check'] = -- TODO: need translate! +'Enable diagnostics for typos in strings.' +config.diagnostics['name-style-check'] = -- TODO: need translate! +'ๅ˜้‡็š„ๅ็งฐๆ ทๅผๆฃ€ๆŸฅ' +config.diagnostics['unbalanced-assignments']= +'ๅคš้‡่ต‹ๅ€ผๆ—ถๆฒกๆœ‰่ต‹ๅ€ผๆ‰€ๆœ‰ๅ˜้‡(ๅฆ‚`local x,y = 1`)' +config.diagnostics['undefined-doc-class'] = +'ๅœจ`@class`ๆณจ่งฃไธญๅผ•็”จๆœชๅฎšไน‰็š„็ฑปใ€‚' +config.diagnostics['undefined-doc-name'] = +'ๅœจ`@type`ๆณจ่งฃไธญๅผ•็”จๆœชๅฎšไน‰็š„็ฑปๅž‹ๆˆ–`@alias`' +config.diagnostics['undefined-doc-param'] = +'ๅ‡ฝๆ•ฐๅฃฐๆ˜Žไธญ`@param`ๅผ•็”จไบ†ๆœชๅฎšไน‰็š„ๅ‚ๆ•ฐ' +config.diagnostics['undefined-field'] = +'ๅผ•็”จๅ˜้‡็š„ๆœชๅฎšไน‰ๅญ—ๆฎต' +config.diagnostics['unknown-cast-variable'] = +'ไฝฟ็”จ`@cast`ๅฏนๆœชๅฎšไน‰ๅ˜้‡็š„ๅผบๅˆถ่ฝฌๆข' +config.diagnostics['unknown-diag-code'] = +'ๆœช็Ÿฅ็š„่ฏŠๆ–ญไปฃ็ ' +config.diagnostics['unknown-operator'] = +'ๆœช็Ÿฅ็š„่ฟ็ฎ—็ฌฆ' +config.diagnostics['unreachable-code'] = +'ไธๅฏ่พพ็š„ไปฃ็ ' +config.diagnostics['global-element'] = -- TODO: need translate! +'Enable diagnostics to warn about global elements.' +config.typeFormat.config = +'้…็ฝฎ่พ“ๅ…ฅLuaไปฃ็ ๆ—ถ็š„ๆ ผๅผๅŒ–่กŒไธบ' +config.typeFormat.config.auto_complete_end = +'ๆ˜ฏๅฆๅœจๅˆ้€‚็š„ไฝ็ฝฎ่‡ชๅŠจๅฎŒๆˆ `end`' +config.typeFormat.config.auto_complete_table_sep = +'ๆ˜ฏๅฆๅœจtableๆœซๅฐพ่‡ชๅŠจๆทปๅŠ ๅˆ†้š”็ฌฆ' +config.typeFormat.config.format_line = +'ๆ˜ฏๅฆๅฏนๆŸไธ€่กŒ่ฟ›่กŒๆ ผๅผๅŒ–' + +command.exportDocument = +'Lua: ๅฏผๅ‡บๆ–‡ๆกฃ...' +command.addon_manager.open = +'Lua: ๆ‰“ๅผ€ๆ’ไปถ็ฎก็†ๅ™จ...' +command.reloadFFIMeta = +'Lua: ้‡ๆ–ฐ็”Ÿๆˆluajit็š„FFIๆจกๅ—C่ฏญ่จ€ๅ…ƒๆ•ฐๆฎ' diff --git a/locale/zh-tw/meta.lua b/locale/zh-tw/meta.lua index 23af021af..8aa5656d9 100644 --- a/locale/zh-tw/meta.lua +++ b/locale/zh-tw/meta.lua @@ -498,11 +498,11 @@ math.log10 = 'ๅ›žๅ‚ณ `x` ็š„ไปฅ10็‚บๅบ•็š„ๅฐๆ•ธใ€‚' math.max = 'ๅ›žๅ‚ณๅผ•ๆ•ธไธญๆœ€ๅคง็š„ๅ€ผ๏ผŒๅคงๅฐ็”ฑ Lua ้‹็ฎ—ๅญ `<` ๆฑบๅฎšใ€‚' -math.maxinteger = +math.maxinteger['>5.3'] = 'ๆœ€ๅคงๅ€ผ็š„ๆ•ดๆ•ธใ€‚' math.min = 'ๅ›žๅ‚ณๅผ•ๆ•ธไธญๆœ€ๅฐ็š„ๅ€ผ๏ผŒๅคงๅฐ็”ฑ Lua ้‹็ฎ—ๅญ `<` ๆฑบๅฎšใ€‚' -math.mininteger = +math.mininteger['>5.3'] = 'ๆœ€ๅฐๅ€ผ็š„ๆ•ดๆ•ธใ€‚' math.modf = 'ๅ›žๅ‚ณ `x` ็š„ๆ•ดๆ•ธ้ƒจๅˆ†ๅ’Œๅฐๆ•ธ้ƒจๅˆ†ใ€‚' @@ -536,11 +536,11 @@ math.tan = 'ๅ›žๅ‚ณ `x` ็š„ๆญฃๅˆ‡ๅ€ผ๏ผˆๅ‡ๅฎšๅผ•ๆ•ธๆ˜ฏๅผงๅบฆ๏ผ‰ใ€‚' math.tanh = 'ๅ›žๅ‚ณ `x` ็š„้›™ๆ›ฒๆญฃๅˆ‡ๅ€ผ๏ผˆๅ‡ๅฎšๅผ•ๆ•ธๆ˜ฏๅผงๅบฆ๏ผ‰ใ€‚' -math.tointeger = +math.tointeger['>5.3'] = 'ๅฆ‚ๆžœ `x` ๅฏไปฅ่ฝ‰ๆ›็‚บไธ€ๅ€‹ๆ•ดๆ•ธ๏ผŒๅ›žๅ‚ณ่ฉฒๆ•ดๆ•ธใ€‚' -math.type = +math.type['>5.3'] = 'ๅฆ‚ๆžœ `x` ๆ˜ฏๆ•ดๆ•ธ๏ผŒๅ›žๅ‚ณ `"integer"` ๏ผŒๅฆ‚ๆžœๅฎƒๆ˜ฏๆตฎ้ปžๆ•ธ๏ผŒๅ›žๅ‚ณ `"float"` ๏ผŒๅฆ‚ๆžœ `x` ไธๆ˜ฏๆ•ธๅญ—๏ผŒๅ›žๅ‚ณ `nil` ใ€‚' -math.ult = +math.ult['>5.3'] = 'ๆ•ดๆ•ธ `m` ๅ’Œ `n` ไปฅ็„ก็ฌฆ่™Ÿๆ•ดๆ•ธๅฝขๅผๆฏ”่ผƒ๏ผŒๅฆ‚ๆžœ `m` ๅœจ `n` ไน‹ไธ‹ๅ‰‡ๅ›žๅ‚ณๅธƒๆž—็œŸ๏ผŒๅฆๅ‰‡ๅ›žๅ‚ณๅ‡ใ€‚' os = diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua index bb6ab94e0..43c064b20 100644 --- a/locale/zh-tw/script.lua +++ b/locale/zh-tw/script.lua @@ -114,6 +114,22 @@ DIAG_UNDEFINED_DOC_NAME = 'ๆœชๅฎš็พฉ็š„้กžๅž‹ๆˆ–ๅˆฅๅ `{}`ใ€‚' DIAG_UNDEFINED_DOC_PARAM = 'ๆŒ‡ๅ‘ไบ†ๆœชๅฎš็พฉ็š„ๅƒๆ•ธ `{}`ใ€‚' +DIAG_MISSING_GLOBAL_DOC_COMMENT = -- TODO: need translate! +'Missing comment for global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_PARAM = -- TODO: need translate! +'Missing @param annotation for parameter `{}` in global function `{}`.' +DIAG_MISSING_GLOBAL_DOC_RETURN = -- TODO: need translate! +'Missing @return annotation at index `{}` in global function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT = -- TODO: need translate! +'Missing comment for exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM = -- TODO: need translate! +'Missing @param annotation for parameter `{}` in exported local function `{}`.' +DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN = -- TODO: need translate! +'Missing @return annotation at index `{}` in exported local function `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM = -- TODO: need translate! +'Incomplete signature. Missing @param annotation for parameter `{}`.' +DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN = -- TODO: need translate! +'Incomplete signature. Missing @return annotation at index `{}`.' DIAG_UNKNOWN_DIAG_CODE = 'ๆœช็Ÿฅ็š„่จบๆ–ทไปฃ็ขผ `{}`ใ€‚' DIAG_CAST_LOCAL_TYPE = @@ -139,11 +155,27 @@ DIAG_REDUNDANT_RETURN_VALUE_RANGE = DIAG_MISSING_RETURN = 'ๆญค่™•้œ€่ฆๅ›žๅ‚ณๅ€ผใ€‚' DIAG_RETURN_TYPE_MISMATCH = -'็ฌฌ {index} ๅ€‹ๅ›žๅ‚ณๅ€ผ็š„้กžๅž‹็‚บ `{def}` ๏ผŒไฝ†ๅฏฆ้š›ๅ›žๅ‚ณ็š„ๆ˜ฏ `{ref}`ใ€‚' +'็ฌฌ {index} ๅ€‹ๅ›žๅ‚ณๅ€ผ็š„้กžๅž‹็‚บ `{def}` ๏ผŒไฝ†ๅฏฆ้š›ๅ›žๅ‚ณ็š„ๆ˜ฏ `{ref}`ใ€‚\n{err}' DIAG_UNKNOWN_OPERATOR = -- TODO: need translate! 'Unknown operator `{}`.' DIAG_UNREACHABLE_CODE = -- TODO: need translate! 'Unreachable code.' +DIAG_INVISIBLE_PRIVATE = -- TODO: need translate! +'Field `{field}` is private, it can only be accessed in class `{class}`.' +DIAG_INVISIBLE_PROTECTED = -- TODO: need translate! +'Field `{field}` is protected, it can only be accessed in class `{class}` and its subclasses.' +DIAG_INVISIBLE_PACKAGE = -- TODO: need translate! +'Field `{field}` can only be accessed in same file `{uri}`.' +DIAG_GLOBAL_ELEMENT = -- TODO: need translate! +'Element is global.' +DIAG_MISSING_FIELDS = -- TODO: need translate! +'Missing required fields in type `{1}`: {2}' +DIAG_INJECT_FIELD = -- TODO: need translate! +'Fields cannot be injected into the reference of `{class}` for `{field}`. {fix}' +DIAG_INJECT_FIELD_FIX_CLASS = -- TODO: need translate! +'To do so, use `---@class` for `{node}`.' +DIAG_INJECT_FIELD_FIX_TABLE = -- TODO: need translate! +'ๅฆ‚่ฆๅ…่ฎธๆณจๅ…ฅ๏ผŒ่ฏทๅœจๅฎšไน‰ไธญๆทปๅŠ  `{fix}` ใ€‚' MWS_NOT_SUPPORT = '{} ็›ฎๅ‰้‚„ไธๆ”ฏๆดๅคšๅทฅไฝœ็›ฎ้Œ„๏ผŒๆˆ‘ๅฏ่ƒฝ้œ€่ฆ้‡ๆ–ฐๅ•Ÿๅ‹•ๆ‰่ƒฝๆ”ฏๆดๆ–ฐ็š„ๅทฅไฝœ็›ฎ้Œ„...' @@ -171,9 +203,9 @@ WORKSPACE_DIAGNOSTIC = WORKSPACE_SKIP_HUGE_FILE = 'ๅ‡บๆ–ผๆ•ˆ่ƒฝ่€ƒๆ…ฎ๏ผŒๅทฒๅœๆญขๅฐๆญคๆช”ๆกˆ่งฃๆž๏ผš{}' WORKSPACE_NOT_ALLOWED = -'ไฝ ็š„ๅทฅไฝœ็›ฎ้Œ„่ขซ่จญๅฎš็‚บไบ† `{}` ๏ผŒLua่ชž่จ€ไผบๆœๆ‹’็ต•่ผ‰ๅ…ฅๆญค็›ฎ้Œ„๏ผŒ่ซ‹ๆชขๆŸฅไฝ ็š„่จญๅฎšๆช”ใ€‚[ไบ†่งฃๆ›ดๅคš](https://github.com/sumneko/lua-language-server/wiki/FAQ#why-is-the-server-scanning-the-wrong-folder)' -WORKSPACE_SCAN_TOO_MUCH = -'ๅทฒๆŽƒๆไบ†่ถ…้Ž {} ๅ€‹ๆช”ๆกˆ๏ผŒ็›ฎๅ‰ๆŽƒๆ็š„็›ฎ้Œ„็‚บ `{}`๏ผŒ่ซ‹็ขบ่ช่จญๅฎšๆช”ๆ˜ฏๅฆๆญฃ็ขบใ€‚' +'ไฝ ็š„ๅทฅไฝœ็›ฎ้Œ„่ขซ่จญๅฎš็‚บไบ† `{}` ๏ผŒLua่ชž่จ€ไผบๆœๆ‹’็ต•่ผ‰ๅ…ฅๆญค็›ฎ้Œ„๏ผŒ่ซ‹ๆชขๆŸฅไฝ ็š„่จญๅฎšๆช”ใ€‚[ไบ†่งฃๆ›ดๅคš](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder)' +WORKSPACE_SCAN_TOO_MUCH = -- TODO: need translate! +'ๅทฒๆŽƒๆไบ†่ถ…้Ž {} ๅ€‹ๆช”ๆกˆ๏ผŒ็›ฎๅ‰ๆŽƒๆ็š„็›ฎ้Œ„็‚บ `{}`. Please see the [FAQ](https://luals.github.io/wiki/faq#how-can-i-improve-startup-speeds) to see how you can include fewer files. It is also possible that your [configuration is incorrect](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder).' PARSER_CRASH = '่ชžๆณ•่งฃๆžๅดฉๆฝฐไบ†๏ผ้บ่จ€๏ผš{}' @@ -269,6 +301,14 @@ PARSER_INDEX_IN_FUNC_NAME = 'ๅ‘ฝๅๅ‡ฝๅผ็š„ๅ็จฑไธญไธ่ƒฝไฝฟ็”จ `[name]` ๅฝขๅผใ€‚' PARSER_UNKNOWN_ATTRIBUTE = 'ๅ€ๅŸŸ่ฎŠๆ•ธๅฑฌๆ€งๆ‡‰่ฉฒๆ˜ฏ `const` ๆˆ– `close` ใ€‚' +PARSER_AMBIGUOUS_SYNTAX = -- TODO: need translate! +'ๅœจ Lua 5.1 ไธญ๏ผŒๅ‡ฝๆ•ฐ่ฐƒ็”จ็š„ๅทฆๆ‹ฌๅทๅฟ…้กปไธŽๅ‡ฝๆ•ฐๅœจๅŒไธ€่กŒใ€‚' +PARSER_NEED_PAREN = -- TODO: need translate! +'้œ€่ฆๆทปๅŠ ไธ€ๅฏนๆ‹ฌๅทใ€‚' +PARSER_NESTING_LONG_MARK = -- TODO: need translate! +'Nesting of `[[...]]` is not allowed in Lua 5.1 .' +PARSER_LOCAL_LIMIT = -- TODO: need translate! +'Only 200 active local variables and upvalues can be existed at the same time.' PARSER_LUADOC_MISS_CLASS_NAME = '็ผบๅฐ‘้กžๅˆฅๅ็จฑใ€‚' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = @@ -414,6 +454,10 @@ ACTION_MARK_ASYNC = 'ๅฐ‡็›ฎๅ‰ๅ‡ฝๅผๆจ™่จ˜็‚บ้žๅŒๆญฅใ€‚' ACTION_ADD_DICT = 'ๆทปๅŠ  \'{}\' ๅˆฐๅทฅไฝœๅ€ๅญ—ๅ…ธ' +ACTION_FIX_ADD_PAREN = -- TODO: need translate! +'ๆทปๅŠ ๆ‹ฌๅทใ€‚' +ACTION_AUTOREQUIRE = -- TODO: need translate! +"Import '{}' as {}" COMMAND_DISABLE_DIAG = 'ๅœ็”จ่จบๆ–ท' @@ -435,6 +479,8 @@ COMMAND_JSON_TO_LUA_FAILED = 'JSON ่ฝ‰ Lua ๅคฑๆ•—๏ผš{}' COMMAND_ADD_DICT = 'ๆทปๅŠ ๅ–ฎๅญ—ๅˆฐๅญ—ๅ…ธ่ฃก' +COMMAND_REFERENCE_COUNT = -- TODO: need translate! +'{} references' COMPLETION_IMPORT_FROM = 'ๅพž {} ไธญๅŒฏๅ…ฅ' @@ -512,7 +558,7 @@ WINDOW_APPLY_SETTING = WINDOW_CHECK_SEMANTIC = 'ๅฆ‚ๆžœไฝ ๆญฃๅœจไฝฟ็”จๅธ‚ๅ ดไธญ็š„้ก่‰ฒไธป้กŒ๏ผŒไฝ ๅฏ่ƒฝ้œ€่ฆๅŒๆ™‚ไฟฎๆ”น `editor.semanticHighlighting.enabled` ้ธ้ …็‚บ `true` ๆ‰ๆœƒไฝฟ่ชž็พฉ่‘—่‰ฒ็”Ÿๆ•ˆใ€‚' WINDOW_TELEMETRY_HINT = -'่ซ‹ๅ…่จฑ็™ผ้€ๅŒฟๅ็š„ไฝฟ็”จ่ณ‡ๆ–™่ˆ‡้Œฏ่ชคๅ ฑๅ‘Š๏ผŒๅนซๅŠฉๆˆ‘ๅ€‘้€ฒไธ€ๆญฅๅฎŒๅ–„ๆญคๅปถไผธๆจก็ต„ใ€‚ๅœจ[ๆญค่™•](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้–ฑ่ฎ€ๆˆ‘ๅ€‘็š„้šฑ็ง่ฒๆ˜Žใ€‚' +'่ซ‹ๅ…่จฑ็™ผ้€ๅŒฟๅ็š„ไฝฟ็”จ่ณ‡ๆ–™่ˆ‡้Œฏ่ชคๅ ฑๅ‘Š๏ผŒๅนซๅŠฉๆˆ‘ๅ€‘้€ฒไธ€ๆญฅๅฎŒๅ–„ๆญคๅปถไผธๆจก็ต„ใ€‚ๅœจ[ๆญค่™•](https://luals.github.io/privacy/#language-server)้–ฑ่ฎ€ๆˆ‘ๅ€‘็š„้šฑ็ง่ฒๆ˜Žใ€‚' WINDOW_TELEMETRY_ENABLE = 'ๅ…่จฑ' WINDOW_TELEMETRY_DISABLE = @@ -535,6 +581,14 @@ WINDOW_ASK_APPLY_LIBRARY = 'ๆ˜ฏๅฆ้œ€่ฆๅฐ‡ไฝ ็š„ๅทฅไฝœ็’ฐๅขƒ้…็ฝฎ็‚บ `{}` ๏ผŸ' WINDOW_SEARCHING_IN_FILES = 'ๆญฃๅœจๆช”ๆกˆไธญๆœๅฐ‹...' +WINDOW_CONFIG_LUA_DEPRECATED = -- TODO: need translate! +'`config.lua` is deprecated, please use `config.json` instead.' +WINDOW_CONVERT_CONFIG_LUA = -- TODO: need translate! +'Convert to `config.json`' +WINDOW_MODIFY_REQUIRE_PATH = -- TODO: need translate! +'Do you want to modify the require path?' +WINDOW_MODIFY_REQUIRE_OK = -- TODO: need translate! +'Modify' CONFIG_LOAD_FAILED = '็„กๆณ•่ฎ€ๅ–่จญๅฎšๆช”ๆกˆ๏ผš{}' @@ -542,6 +596,24 @@ CONFIG_LOAD_ERROR = '่จญๅฎšๆช”ๆกˆ่ผ‰ๅ…ฅ้Œฏ่ชค๏ผš{}' CONFIG_TYPE_ERROR = '่จญๅฎšๆช”ๆกˆๅฟ…้ ˆๆ˜ฏluaๆˆ–jsonๆ ผๅผ๏ผš{}' +CONFIG_MODIFY_FAIL_SYNTAX_ERROR = -- TODO: need translate! +'Failed to modify settings, there are syntax errors in the settings file: {}' +CONFIG_MODIFY_FAIL_NO_WORKSPACE = -- TODO: need translate! +[[ +Failed to modify settings: +* The current mode is single-file mode, server cannot create `.luarc.json` without workspace. +* The language client dose not support modifying settings from the server side. + +Please modify following settings manually: +{} +]] +CONFIG_MODIFY_FAIL = -- TODO: need translate! +[[ +Failed to modify settings + +Please modify following settings manually: +{} +]] PLUGIN_RUNTIME_ERROR = [[ @@ -576,6 +648,49 @@ CLI_CHECK_SUCCESS = '่จบๆ–ทๅฎŒๆˆ๏ผŒๆฒ’ๆœ‰็™ผ็พๅ•้กŒ' CLI_CHECK_RESULTS = '่จบๆ–ทๅฎŒๆˆ๏ผŒๅ…ฑๆœ‰ {} ๅ€‹ๅ•้กŒ๏ผŒ่ซ‹ๆŸฅ็œ‹ {}' +CLI_DOC_INITING = -- TODO: need translate! +'Loading documents ...' +CLI_DOC_DONE = -- TODO: need translate! +[[ +Document exporting completed! +Raw data: {} +Markdown(example): {} +]] + +TYPE_ERROR_ENUM_GLOBAL_DISMATCH = -- TODO: need translate! +'Type `{child}` cannot match enumeration type of `{parent}`' +TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED = -- TODO: need translate! +'Cannot use generic `{child}` in enumeration' +TYPE_ERROR_ENUM_LITERAL_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match the enumeration value of `{parent}`' +TYPE_ERROR_ENUM_OBJECT_DISMATCH = -- TODO: need translate! +'The object `{child}` cannot match the enumeration value of `{parent}`. They must be the same object' +TYPE_ERROR_ENUM_NO_OBJECT = -- TODO: need translate! +'The passed in enumeration value `{child}` is not recognized' +TYPE_ERROR_INTEGER_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match integer `{parent}`' +TYPE_ERROR_STRING_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match string `{parent}`' +TYPE_ERROR_BOOLEAN_DISMATCH = -- TODO: need translate! +'Literal `{child}` cannot match boolean `{parent}`' +TYPE_ERROR_TABLE_NO_FIELD = -- TODO: need translate! +'Field `{key}` does not exist in the table' +TYPE_ERROR_TABLE_FIELD_DISMATCH = -- TODO: need translate! +'The type of field `{key}` is `{child}`, which cannot match `{parent}`' +TYPE_ERROR_CHILD_ALL_DISMATCH = -- TODO: need translate! +'All subtypes in `{child}` cannot match `{parent}`' +TYPE_ERROR_PARENT_ALL_DISMATCH = -- TODO: need translate! +'`{child}` cannot match any subtypes in `{parent}`' +TYPE_ERROR_UNION_DISMATCH = -- TODO: need translate! +'`{child}` cannot match `{parent}`' +TYPE_ERROR_OPTIONAL_DISMATCH = -- TODO: need translate! +'Optional type cannot match `{parent}`' +TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = -- TODO: need translate! +'The number `{child}` cannot be converted to an integer' +TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = -- TODO: need translate! +'Cannot convert number type to integer type' +TYPE_ERROR_DISMATCH = -- TODO: need translate! +'Type `{child}` cannot match `{parent}`' LUADOC_DESC_CLASS = [=[ @@ -588,7 +703,7 @@ LUADOC_DESC_CLASS = Manager = {} ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#class) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#class) ]=] LUADOC_DESC_TYPE = [=[ @@ -639,7 +754,7 @@ local x --x[""] is true local myFunction ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#types-and-type) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#type) ]=] LUADOC_DESC_ALIAS = [=[ @@ -674,8 +789,22 @@ function find(path, pattern) end ---@param style font-style Style to apply function setFontStyle(style) end ``` + +### Literal Enum +``` +local enums = { + READ = 0, + WRITE = 1, + CLOSED = 2 +} + +---@alias FileStates +---| `enums.READ` +---| `enums.WRITE` +---| `enums.CLOSE` +``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#alias) ]=] LUADOC_DESC_PARAM = [=[ @@ -700,7 +829,7 @@ function get(url, headers, timeout) end function concat(base, ...) end ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#param) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#param) ]=] LUADOC_DESC_RETURN = [=[ @@ -738,7 +867,7 @@ function getFirstLast() end function getTags(item) end ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#return) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#return) ]=] LUADOC_DESC_FIELD = [=[ @@ -767,7 +896,7 @@ response = get("localhost") statusCode = response.status.code ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#field) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#field) ]=] LUADOC_DESC_GENERIC = [=[ @@ -823,7 +952,7 @@ local v = Generic("Foo") -- v is an object of Foo -- we give for key (K) or value (V) ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#generics-and-generic) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#generic) ]=] LUADOC_DESC_VARARG = [=[ @@ -841,7 +970,7 @@ LUADOC_DESC_VARARG = function concat(...) end ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#vararg) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#vararg) ]=] LUADOC_DESC_OVERLOAD = [=[ @@ -856,7 +985,7 @@ LUADOC_DESC_OVERLOAD = function table.insert(t, position, value) end ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#overload) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#overload) ]=] LUADOC_DESC_DEPRECATED = [=[ @@ -866,7 +995,7 @@ LUADOC_DESC_DEPRECATED = `---@deprecated` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#deprecated) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#deprecated) ]=] LUADOC_DESC_META = [=[ @@ -881,7 +1010,7 @@ LUADOC_DESC_META = `---@meta` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#meta) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#meta) ]=] LUADOC_DESC_VERSION = [=[ @@ -906,7 +1035,7 @@ function onlyWorksInJIT() end function oldLuaOnly() end ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#version) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#version) ]=] LUADOC_DESC_SEE = [=[ @@ -916,7 +1045,7 @@ LUADOC_DESC_SEE = `---@see ` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#see) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#see) ]=] LUADOC_DESC_DIAGNOSTIC = [=[ @@ -924,7 +1053,7 @@ LUADOC_DESC_DIAGNOSTIC = ๆ“ไฝœ๏ผš`disable` ใ€ `enable` ใ€ `disable-line` ใ€ `disable-next-line` -[ๅ็จฑ](https://github.com/sumneko/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) +[ๅ็จฑ](https://github.com/LuaLS/lua-language-server/blob/cbb6e6224094c4eb874ea192c5f85a6cba099588/script/proto/define.lua#L54) ## ่ชžๆณ• `---@diagnostic [: ]` @@ -942,7 +1071,7 @@ local unused = "hello world" ---@diagnostic enable: unused-local ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#diagnostic) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#diagnostic) ]=] LUADOC_DESC_MODULE = [=[ @@ -959,7 +1088,7 @@ local stringUtils local module = require('string.utils') ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#module) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#module) ]=] LUADOC_DESC_ASYNC = [=[ @@ -969,7 +1098,7 @@ LUADOC_DESC_ASYNC = `---@async` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#async) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#async) ]=] LUADOC_DESC_NODISCARD = [=[ @@ -980,7 +1109,7 @@ LUADOC_DESC_NODISCARD = `---@nodiscard` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#nodiscard) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#nodiscard) ]=] LUADOC_DESC_CAST = [=[ @@ -1015,7 +1144,7 @@ local x --> string|table print(x) --> table ``` --- -[ๆชข่ฆ–ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Annotations#cast) +[ๆชข่ฆ–ๆ–‡ไปถ](https://luals.github.io/wiki/annotations#cast) ]=] LUADOC_DESC_OPERATOR = -- TODO: need translate! [=[ @@ -1045,12 +1174,12 @@ pA = Passcode.new(1234) pB = -pA --> integer ``` -[View Request](https://github.com/sumneko/lua-language-server/issues/599) +[View Request](https://github.com/LuaLS/lua-language-server/issues/599) ]=] LUADOC_DESC_ENUM = -- TODO: need translate! [=[ Mark a table as an enum. If you want an enum but can't define it as a Lua -table, take a look at the [`@alias`](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias) +table, take a look at the [`@alias`](https://luals.github.io/wiki/annotations#alias) tag. ## Syntax @@ -1074,3 +1203,103 @@ local function setColor(color) end setColor(colors.green) ``` ]=] +LUADOC_DESC_SOURCE = -- TODO: need translate! +[=[ +Provide a reference to some source code which lives in another file. When +searching for the defintion of an item, its `@source` will be used. + +## Syntax +`@source ` + +## Usage +``` +---You can use absolute paths +---@source C:/Users/me/Documents/program/myFile.c +local a + +---Or URIs +---@source file:///C:/Users/me/Documents/program/myFile.c:10 +local b + +---Or relative paths +---@source local/file.c +local c + +---You can also include line and char numbers +---@source local/file.c:10:8 +local d +``` +]=] +LUADOC_DESC_PACKAGE = -- TODO: need translate! +[=[ +Mark a function as private to the file it is defined in. A packaged function +cannot be accessed from another file. + +## Syntax +`@package` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@package +---This cannot be accessed in another file +function Animal:eyesCount() + return self.eyes +end +``` +]=] +LUADOC_DESC_PRIVATE = -- TODO: need translate! +[=[ +Mark a function as private to a @class. Private functions can be accessed only +from within their class and are not accessable from child classes. + +## Syntax +`@private` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@private +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---NOT PERMITTED! +myDog:eyesCount(); +``` +]=] +LUADOC_DESC_PROTECTED = -- TODO: need translate! +[=[ +Mark a function as protected within a @class. Protected functions can be +accessed only from within their class or from child classes. + +## Syntax +`@protected` + +## Usage +``` +---@class Animal +---@field private eyes integer +local Animal = {} + +---@protected +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---Permitted because function is protected, not private. +myDog:eyesCount(); +``` +]=] diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua index 37269484d..2b43e954c 100644 --- a/locale/zh-tw/setting.lua +++ b/locale/zh-tw/setting.lua @@ -1,5 +1,7 @@ ---@diagnostic disable: undefined-global +config.addonManager.enable = -- TODO: need translate! +"Whether the addon manager is enabled or not." config.runtime.version = "LuaๅŸท่กŒ็‰ˆๆœฌใ€‚" config.runtime.path = @@ -25,7 +27,7 @@ config.runtime.unicodeName = config.runtime.nonstandardSymbol = "ๆ”ฏๆด้žๆจ™ๆบ–็š„็ฌฆ่™Ÿใ€‚่ซ‹ๅ‹™ๅฟ…็ขบ่ชไฝ ็š„ๅŸท่กŒ็’ฐๅขƒๆ”ฏๆด้€™ไบ›็ฌฆ่™Ÿใ€‚" config.runtime.plugin = -"ๅปถไผธๆจก็ต„่ทฏๅพ‘๏ผŒ่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/Plugins)็žญ่งฃ็”จๆณ•ใ€‚" +"ๅปถไผธๆจก็ต„่ทฏๅพ‘๏ผŒ่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://luals.github.io/wiki/plugins)็žญ่งฃ็”จๆณ•ใ€‚" config.runtime.pluginArgs = -- TODO: need translate! "Additional arguments for the plugin." config.runtime.fileEncoding = @@ -76,8 +78,16 @@ config.diagnostics.groupFileStatus = ่จญๅฎš็‚บ `Fallback` ๆ„ๅ‘ณ่‘—็ต„ไธญ็š„่จบๆ–ท็”ฑ `diagnostics.neededFileStatus` ๅ–ฎ็จ่จญๅฎšใ€‚ ๅ…ถไป–่จญๅฎšๅฐ‡่ฆ†่“‹ๅ–ฎ็จ่จญๅฎš๏ผŒไฝ†ๆ˜ฏไธๆœƒ่ฆ†่“‹ไปฅ `!` ็ตๅฐพ็š„่จญๅฎšใ€‚ ]] +config.diagnostics.workspaceEvent = -- TODO: need translate! +"Set the time to trigger workspace diagnostics." +config.diagnostics.workspaceEvent.OnChange = -- TODO: need translate! +"Trigger workspace diagnostics when the file is changed." +config.diagnostics.workspaceEvent.OnSave = -- TODO: need translate! +"Trigger workspace diagnostics when the file is saved." +config.diagnostics.workspaceEvent.None = -- TODO: need translate! +"Disable workspace diagnostics." config.diagnostics.workspaceDelay = -"้€ฒ่กŒๅทฅไฝœๅ€่จบๆ–ท็š„ๅปถ้ฒ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚็•ถไฝ ๅ•Ÿๅ‹•ๅทฅไฝœๅ€๏ผŒๆˆ–็ทจ่ผฏไบ†ไปปไฝ•ๆช”ๆกˆๅพŒ๏ผŒๅฐ‡ๆœƒๅœจ่ƒŒๆ™ฏๅฐๆ•ดๅ€‹ๅทฅไฝœๅ€้€ฒ่กŒ้‡ๆ–ฐ่จบๆ–ทใ€‚่จญๅฎš็‚บ่ฒ ๆ•ธๅฏไปฅๅœ็”จๅทฅไฝœๅ€่จบๆ–ทใ€‚" +"้€ฒ่กŒๅทฅไฝœๅ€่จบๆ–ท็š„ๅปถ้ฒ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚" config.diagnostics.workspaceRate = "ๅทฅไฝœๅ€่จบๆ–ท็š„ๅŸท่กŒ้€Ÿ็އ๏ผˆ็™พๅˆ†ๆฏ”๏ผ‰ใ€‚้™ไฝŽ่ฉฒๅ€ผๆœƒๆธ›ๅฐ‘CPUไฝฟ็”จ็އ๏ผŒไฝ†ๆ˜ฏไนŸๆœƒ้™ไฝŽๅทฅไฝœๅ€่จบๆ–ท็š„้€Ÿๅบฆใ€‚ไฝ ็›ฎๅ‰ๆญฃๅœจ็ทจ่ผฏ็š„ๆช”ๆกˆ็š„่จบๆ–ท็ธฝๆ˜ฏๅ…จ้€ŸๅฎŒๆˆ๏ผŒไธๅ—่ฉฒ้ธ้ …ๅฝฑ้Ÿฟใ€‚" config.diagnostics.libraryFiles = @@ -124,7 +134,7 @@ config.workspace.checkThirdParty = * Jass ]] config.workspace.userThirdParty = -'ๅœจ้€™่ฃกๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบซ้ฉๆ‡‰ๆช”ๆกˆ่ทฏๅพ‘๏ผŒ่ซ‹ๅƒ่€ƒๅ…งๅปบ็š„[็ต„ๆ…‹ๆช”ๆกˆ่ทฏๅพ‘](https://github.com/sumneko/lua-language-server/tree/master/meta/3rd)' +'ๅœจ้€™่ฃกๆทปๅŠ ็งๆœ‰็š„็ฌฌไธ‰ๆ–นๅบซ้ฉๆ‡‰ๆช”ๆกˆ่ทฏๅพ‘๏ผŒ่ซ‹ๅƒ่€ƒๅ…งๅปบ็š„[็ต„ๆ…‹ๆช”ๆกˆ่ทฏๅพ‘](https://github.com/LuaLS/lua-language-server/tree/master/meta/3rd)' config.workspace.supportScheme = '็‚บไปฅไธ‹ `scheme` ็š„luaๆช”ๆกˆๆไพ›่ชž่จ€ไผบๆœใ€‚' config.completion.enable = @@ -247,6 +257,8 @@ config.hint.semicolon.SameLine = 'ๅ…ฉๅ€‹้™ณ่ฟฐๅผๅœจๅŒไธ€่กŒๆ™‚๏ผŒๅœจๅฎƒๅ€‘ไน‹้–“้กฏ็คบๅˆ†่™Ÿใ€‚' config.hint.semicolon.Disable = 'ๅœ็”จ่™›ๆ“ฌๅˆ†่™Ÿใ€‚' +config.codeLens.enable = -- TODO: need translate! +'Enable code lens.' config.format.enable = 'ๅ•Ÿ็”จ็จ‹ๅผ็ขผๆ ผๅผๅŒ–็จ‹ๅผใ€‚' config.format.defaultConfig = @@ -256,20 +268,16 @@ config.format.defaultConfig = ]] config.spell.dict = 'ๆ‹ผๅฏซๆชขๆŸฅ็š„่‡ช่จ‚ๅ–ฎ่ฉžใ€‚' +config.nameStyle.config = -- TODO: need translate! +'Set name style config' config.telemetry.enable = [[ -ๅ•Ÿ็”จ้™ๆธฌ๏ผŒ้€้Ž็ถฒ่ทฏ็™ผ้€ไฝ ็š„็ทจ่ผฏๅ™จ่ณ‡่จŠ่ˆ‡้Œฏ่ชคๆ—ฅ่ชŒใ€‚ๅœจ[ๆญค่™•](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)้–ฑ่ฎ€ๆˆ‘ๅ€‘็š„้šฑ็ง่ฒๆ˜Žใ€‚ +ๅ•Ÿ็”จ้™ๆธฌ๏ผŒ้€้Ž็ถฒ่ทฏ็™ผ้€ไฝ ็š„็ทจ่ผฏๅ™จ่ณ‡่จŠ่ˆ‡้Œฏ่ชคๆ—ฅ่ชŒใ€‚ๅœจ[ๆญค่™•](https://luals.github.io/privacy/#language-server)้–ฑ่ฎ€ๆˆ‘ๅ€‘็š„้šฑ็ง่ฒๆ˜Žใ€‚ ]] config.misc.parameters = -'VSCodeไธญๅ•Ÿๅ‹•่ชž่จ€ไผบๆœๆ™‚็š„[ๅ‘ฝไปคๅˆ—ๅƒๆ•ธ](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)ใ€‚' -config.IntelliSense.traceLocalSet = -'่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)็žญ่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceReturn = -'่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)็žญ่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceBeSetted = -'่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)็žญ่งฃ็”จๆณ•ใ€‚' -config.IntelliSense.traceFieldInject = -'่ซ‹ๆŸฅ้–ฑ[ๆ–‡ไปถ](https://github.com/sumneko/lua-language-server/wiki/IntelliSense-optional-features)็žญ่งฃ็”จๆณ•ใ€‚' +'VSCodeไธญๅ•Ÿๅ‹•่ชž่จ€ไผบๆœๆ™‚็š„[ๅ‘ฝไปคๅˆ—ๅƒๆ•ธ](https://luals.github.io/wiki/usage#arguments)ใ€‚' +config.misc.executablePath = -- TODO: need translate! +'Specify the executable path in VSCode.' config.type.castNumberToInteger = 'ๅ…่จฑๅฐ‡ `number` ้กžๅž‹่ณฆๅ€ผ็ตฆ `integer` ้กžๅž‹ใ€‚' config.type.weakUnionCheck = @@ -284,6 +292,18 @@ When checking the type of union type, ignore the `nil` in it. When this setting is `false`, the `number|nil` type cannot be assigned to the `number` type. It can be with `true`. ]] +config.type.inferParamType = -- TODO: need translate! +[[ +ๆœชๆณจ้‡Šๅ‚ๆ•ฐ็ฑปๅž‹ๆ—ถ๏ผŒๅ‚ๆ•ฐ็ฑปๅž‹็”ฑๅ‡ฝๆ•ฐไผ ๅ…ฅๅ‚ๆ•ฐๆŽจๆ–ญใ€‚ + +ๅฆ‚ๆžœ่ฎพ็ฝฎไธบ "false"๏ผŒๅˆ™ๅœจๆœชๆณจ้‡Šๆ—ถ๏ผŒๅ‚ๆ•ฐ็ฑปๅž‹ไธบ "any"ใ€‚ +]] +config.doc.privateName = -- TODO: need translate! +'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.' +config.doc.protectedName = -- TODO: need translate! +'Treat specific field names as protected, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are protected, witch can only be accessed in the class where the definition is located and its subclasses.' +config.doc.packageName = -- TODO: need translate! +'Treat specific field names as package, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are package, witch can only be accessed in the file where the definition is located.' config.diagnostics['unused-local'] = 'ๆœชไฝฟ็”จ็š„ๅ€ๅŸŸ่ฎŠๆ•ธ' config.diagnostics['unused-function'] = @@ -318,3 +338,104 @@ config.diagnostics['empty-block'] = '็ฉบ็จ‹ๅผ็ขผๅ€ๅกŠ' config.diagnostics['redundant-value'] = '่ณฆๅ€ผๆ“ไฝœๆ™‚๏ผŒๅ€ผ็š„ๆ•ธ้‡ๆฏ”่ขซ่ณฆๅ€ผ็š„ๅฐ่ฑกๅคš' +config.diagnostics['assign-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for assignments in which the value\'s type does not match the type of the assigned variable.' +config.diagnostics['await-in-sync'] = -- TODO: need translate! +'Enable diagnostics for calls of asynchronous functions within a synchronous function.' +config.diagnostics['cast-local-type'] = -- TODO: need translate! +'Enable diagnostics for casts of local variables where the target type does not match the defined type.' +config.diagnostics['cast-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for casts where the target type does not match the initial type.' +config.diagnostics['circular-doc-class'] = -- TODO: need translate! +'Enable diagnostics for two classes inheriting from each other introducing a circular relation.' +config.diagnostics['close-non-object'] = -- TODO: need translate! +'Enable diagnostics for attempts to close a variable with a non-object.' +config.diagnostics['code-after-break'] = -- TODO: need translate! +'Enable diagnostics for code placed after a break statement in a loop.' +config.diagnostics['codestyle-check'] = -- TODO: need translate! +'Enable diagnostics for incorrectly styled lines.' +config.diagnostics['count-down-loop'] = -- TODO: need translate! +'Enable diagnostics for `for` loops which will never reach their max/limit because the loop is incrementing instead of decrementing.' +config.diagnostics['deprecated'] = -- TODO: need translate! +'Enable diagnostics to highlight deprecated API.' +config.diagnostics['different-requires'] = -- TODO: need translate! +'Enable diagnostics for files which are required by two different paths.' +config.diagnostics['discard-returns'] = -- TODO: need translate! +'Enable diagnostics for calls of functions annotated with `---@nodiscard` where the return values are ignored.' +config.diagnostics['doc-field-no-class'] = -- TODO: need translate! +'Enable diagnostics to highlight a field annotation without a defining class annotation.' +config.diagnostics['duplicate-doc-alias'] = -- TODO: need translate! +'Enable diagnostics for a duplicated alias annotation name.' +config.diagnostics['duplicate-doc-field'] = -- TODO: need translate! +'Enable diagnostics for a duplicated field annotation name.' +config.diagnostics['duplicate-doc-param'] = -- TODO: need translate! +'Enable diagnostics for a duplicated param annotation name.' +config.diagnostics['duplicate-set-field'] = -- TODO: need translate! +'Enable diagnostics for setting the same field in a class more than once.' +config.diagnostics['incomplete-signature-doc'] = -- TODO: need translate! +'Incomplete @param or @return annotations for functions.' +config.diagnostics['invisible'] = -- TODO: need translate! +'Enable diagnostics for accesses to fields which are invisible.' +config.diagnostics['missing-global-doc'] = -- TODO: need translate! +'Missing annotations for globals! Global functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-local-export-doc'] = -- TODO: need translate! +'Missing annotations for exported locals! Exported local functions must have a comment and annotations for all parameters and return values.' +config.diagnostics['missing-parameter'] = -- TODO: need translate! +'Enable diagnostics for function calls where the number of arguments is less than the number of annotated function parameters.' +config.diagnostics['missing-return'] = -- TODO: need translate! +'Enable diagnostics for functions with return annotations which have no return statement.' +config.diagnostics['missing-return-value'] = -- TODO: need translate! +'Enable diagnostics for return statements without values although the containing function declares returns.' +config.diagnostics['need-check-nil'] = -- TODO: need translate! +'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['no-unknown'] = -- TODO: need translate! +'Enable diagnostics for cases in which the type cannot be inferred.' +config.diagnostics['not-yieldable'] = -- TODO: need translate! +'Enable diagnostics for calls to `coroutine.yield()` when it is not permitted.' +config.diagnostics['param-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for function calls where the type of a provided parameter does not match the type of the annotated function definition.' +config.diagnostics['redundant-return'] = -- TODO: need translate! +'Enable diagnostics for return statements which are not needed because the function would exit on its own.' +config.diagnostics['redundant-return-value']= -- TODO: need translate! +'Enable diagnostics for return statements which return an extra value which is not specified by a return annotation.' +config.diagnostics['return-type-mismatch'] = -- TODO: need translate! +'Enable diagnostics for return values whose type does not match the type declared in the corresponding return annotation.' +config.diagnostics['spell-check'] = -- TODO: need translate! +'Enable diagnostics for typos in strings.' +config.diagnostics['name-style-check'] = -- TODO: need translate! +'Enable diagnostics for name style.' +config.diagnostics['unbalanced-assignments']= -- TODO: need translate! +'Enable diagnostics on multiple assignments if not all variables obtain a value (e.g., `local x,y = 1`).' +config.diagnostics['undefined-doc-class'] = -- TODO: need translate! +'Enable diagnostics for class annotations in which an undefined class is referenced.' +config.diagnostics['undefined-doc-name'] = -- TODO: need translate! +'Enable diagnostics for type annotations referencing an undefined type or alias.' +config.diagnostics['undefined-doc-param'] = -- TODO: need translate! +'Enable diagnostics for cases in which a parameter annotation is given without declaring the parameter in the function definition.' +config.diagnostics['undefined-field'] = -- TODO: need translate! +'Enable diagnostics for cases in which an undefined field of a variable is read.' +config.diagnostics['unknown-cast-variable'] = -- TODO: need translate! +'Enable diagnostics for casts of undefined variables.' +config.diagnostics['unknown-diag-code'] = -- TODO: need translate! +'Enable diagnostics in cases in which an unknown diagnostics code is entered.' +config.diagnostics['unknown-operator'] = -- TODO: need translate! +'Enable diagnostics for unknown operators.' +config.diagnostics['unreachable-code'] = -- TODO: need translate! +'Enable diagnostics for unreachable code.' +config.diagnostics['global-element'] = -- TODO: need translate! +'Enable diagnostics to warn about global elements.' +config.typeFormat.config = -- TODO: need translate! +'Configures the formatting behavior while typing Lua code.' +config.typeFormat.config.auto_complete_end = -- TODO: need translate! +'Controls if `end` is automatically completed at suitable positions.' +config.typeFormat.config.auto_complete_table_sep = -- TODO: need translate! +'Controls if a separator is automatically appended at the end of a table declaration.' +config.typeFormat.config.format_line = -- TODO: need translate! +'Controls if a line is formatted at all.' + +command.exportDocument = -- TODO: need translate! +'Lua: Export Document ...' +command.addon_manager.open = -- TODO: need translate! +'Lua: Open Addon Manager ...' +command.reloadFFIMeta = -- TODO: need translate! +'Lua: Reload luajit ffi meta' diff --git a/main.lua b/main.lua index e26b6cbf7..8ecfd472f 100644 --- a/main.lua +++ b/main.lua @@ -16,9 +16,10 @@ local function getValue(value) end local function loadArgs() + ---@type string? local lastKey for _, v in ipairs(arg) do - ---@type string + ---@type string? local key, tail = v:match '^%-%-([%w_]+)(.*)$' local value if key then @@ -35,7 +36,7 @@ local function loadArgs() end end if key then - _G[key:upper()] = getValue(value) + _G[key:upper():gsub('-', '_')] = getValue(value) end end end @@ -69,10 +70,11 @@ log.info('METAPATH:', METAPATH) log.info('VERSION:', version.getVersion()) require 'tracy' -require 'cli' xpcall(dofile, log.debug, (ROOT / 'debugger.lua'):string()) +require 'cli' + local _, service = xpcall(require, log.error, 'service') service.start() diff --git a/make.bat b/make.bat new file mode 100644 index 000000000..01cb1ddc3 --- /dev/null +++ b/make.bat @@ -0,0 +1,5 @@ +git submodule update --init --recursive +cd 3rd\luamake +call compile\install.bat +cd ..\.. +call 3rd\luamake\luamake.exe rebuild diff --git a/make.lua b/make.lua index c34905d62..f51b6c356 100644 --- a/make.lua +++ b/make.lua @@ -1,17 +1,24 @@ local lm = require 'luamake' -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 = "" +if lm.sanitize then + lm.mode = "debug" + lm.flags = "-fsanitize=address" + lm.gcc = { + ldflags = "-fsanitize=address" + } + lm.clang = { + ldflags = "-fsanitize=address" + } +end local includeCodeFormat = true require "make.detect_platform" -lm:import "3rd/bee.lua" +lm:import "3rd/bee.lua/make.lua" lm:import "make/code_format.lua" lm:source_set 'lpeglabel' { @@ -47,19 +54,28 @@ lm:executable "lua-language-server" { } } +local platform = require 'bee.platform' +local exe = platform.os == 'windows' and ".exe" or "" + +lm:copy "copy_lua-language-server" { + inputs = "$bin/lua-language-server" .. exe, + outputs = "bin/lua-language-server" .. exe, +} + lm:copy "copy_bootstrap" { - input = "make/bootstrap.lua", - output = lm.bindir .. "/main.lua", + inputs = "make/bootstrap.lua", + outputs = "bin/main.lua", } lm:msvc_copydll 'copy_vcrt' { type = "vcrt", - output = lm.bindir, + outputs = "bin", } lm:phony "all" { deps = { "lua-language-server", + "copy_lua-language-server", "copy_bootstrap", }, windows = { @@ -76,26 +92,30 @@ if lm.notest then return end -local platform = require 'bee.platform' -local exe = platform.OS == 'Windows' and ".exe" or "" +lm:rule "run-bee-test" { + args = { "$bin/lua-language-server" .. exe, "$in" }, + description = "Run test: $in.", + pool = "console", +} -lm:build "bee-test" { - lm.bindir .. "/lua-language-server" .. exe, "3rd/bee.lua/test/test.lua", +lm:rule "run-unit-test" { + args = { "bin/lua-language-server" .. exe, "$in" }, + description = "Run test: $in.", pool = "console", - deps = { - "all", - } +} + +lm:build "bee-test" { + rule = "run-bee-test", + deps = { "lua-language-server", "copy_script" }, + inputs = "3rd/bee.lua/test/test.lua", } lm:build 'unit-test' { - lm.bindir .. "/lua-language-server" .. exe, 'test.lua', - pool = "console", - deps = { - "bee-test", - } + rule = "run-unit-test", + deps = { "bee-test", "all" }, + inputs = "test.lua", } lm:default { - "bee-test", "unit-test", } diff --git a/make.sh b/make.sh new file mode 100755 index 000000000..31cca3690 --- /dev/null +++ b/make.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +git submodule update --init --recursive +pushd 3rd/luamake +./compile/build.sh +popd +./3rd/luamake/luamake rebuild diff --git a/make/code_format.lua b/make/code_format.lua index 6cf5cfd7d..3ccf9c8f2 100644 --- a/make/code_format.lua +++ b/make/code_format.lua @@ -1,3 +1,4 @@ +#howdy local lm = require 'luamake' lm.c = lm.compiler == 'msvc' and 'c89' or 'c11' @@ -6,7 +7,9 @@ lm.cxx = 'c++17' lm:source_set 'code_format' { rootdir = '../3rd/EmmyLuaCodeStyle', includes = { - "include", + "Util/include", + "CodeFormatCore/include", + "LuaParser/include", "../bee.lua/3rd/lua", "3rd/wildcards/include" }, @@ -14,32 +17,22 @@ lm:source_set 'code_format' { -- codeFormatLib "CodeFormatLib/src/*.cpp", -- LuaParser - "LuaParser/src/*.cpp", - "LuaParser/src/LuaAstNode/LuaAstNode.cpp", + "LuaParser/src/**/*.cpp", -- Util "Util/src/StringUtil.cpp", "Util/src/Utf8.cpp", - --CodeService - "CodeService/src/*.cpp", - "CodeService/src/Spell/*.cpp", "Util/src/SymSpell/*.cpp", - "CodeService/src/FormatElement/*.cpp", - "CodeService/src/NameStyle/*.cpp" + "Util/src/InfoTree/*.cpp", + --CodeService + "CodeFormatCore/src/**/*.cpp", }, windows = { - -- ไธ่ฆๅผ€ๅ“ฆ - -- flasg = "/W3 /WX" + flags = "/utf-8", }, macos = { 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/detect_platform.lua b/make/detect_platform.lua index 52207f95f..4b62298ff 100644 --- a/make/detect_platform.lua +++ b/make/detect_platform.lua @@ -2,7 +2,7 @@ local lm = require 'luamake' local platform = require 'bee.platform' -if platform.OS == 'macOS' then +if platform.os == 'macos' then if lm.platform == nil then elseif lm.platform == "darwin-arm64" then lm.target = "arm64-apple-macos11" @@ -11,7 +11,7 @@ if platform.OS == 'macOS' then else error "unknown platform" end -elseif platform.OS == 'Windows' then +elseif platform.os == 'windows' then if lm.platform == nil then elseif lm.platform == "win32-ia32" then lm.arch = "x86" @@ -20,7 +20,7 @@ elseif platform.OS == 'Windows' then else error "unknown platform" end -elseif platform.OS == 'Linux' then +elseif platform.os == 'linux' then if lm.platform == nil then elseif lm.platform == "linux-x64" then elseif lm.platform == "linux-arm64" then @@ -52,7 +52,7 @@ local ARCH = { } local function detectArch() - if platform.OS == 'Windows' then + if platform.os == 'windows' then return detectWindowsArch() end local posixArch = detectPosixArch() @@ -67,5 +67,5 @@ local function targetPlatformArch() end if not lm.notest then - lm.notest = (platform.OS ~= 'Windows' and targetPlatformArch() ~= detectArch()) + lm.notest = (platform.os ~= 'windows' and targetPlatformArch() ~= detectArch()) end diff --git a/make/modules.cpp b/make/modules.cpp index 802e69161..8cd6d2d90 100644 --- a/make/modules.cpp +++ b/make/modules.cpp @@ -1,4 +1,4 @@ -#include +#include extern "C" int luaopen_lpeglabel (lua_State *L); static ::bee::lua::callfunc _init(::bee::lua::register_module, "lpeglabel", luaopen_lpeglabel); diff --git a/meta/3rd/Cocos4.0 b/meta/3rd/Cocos4.0 new file mode 160000 index 000000000..c0b2259e0 --- /dev/null +++ b/meta/3rd/Cocos4.0 @@ -0,0 +1 @@ +Subproject commit c0b2259e0d367561fd4563ae114b029b4dfe3a8f diff --git a/meta/3rd/Defold b/meta/3rd/Defold new file mode 160000 index 000000000..05379b40f --- /dev/null +++ b/meta/3rd/Defold @@ -0,0 +1 @@ +Subproject commit 05379b40fb3084d82f7270475e42da8d521c4dae diff --git a/meta/3rd/Jass b/meta/3rd/Jass new file mode 160000 index 000000000..80d85cbbf --- /dev/null +++ b/meta/3rd/Jass @@ -0,0 +1 @@ +Subproject commit 80d85cbbfd8ae2473fb39c7df4be814602f5bc4f diff --git a/meta/3rd/OpenResty b/meta/3rd/OpenResty new file mode 160000 index 000000000..3bec36f0f --- /dev/null +++ b/meta/3rd/OpenResty @@ -0,0 +1 @@ +Subproject commit 3bec36f0f645bb38b3c8208990d5c36feb66ce3d diff --git a/meta/3rd/OpenResty/library/cjson.lua b/meta/3rd/OpenResty/library/cjson.lua deleted file mode 100644 index aeef436a6..000000000 --- a/meta/3rd/OpenResty/library/cjson.lua +++ /dev/null @@ -1,488 +0,0 @@ ----@meta - ---- lua cjson ---- ---- https://kyne.com.au/~mark/software/lua-cjson.php ---- https://kyne.com.au/~mark/software/lua-cjson-manual.html ---- https://openresty.org/en/lua-cjson-library.html ---- ---- **NOTE:** This includes the additions in the OpenResty-maintained cjson fork: https://github.com/openresty/lua-cjson ---- ---- --- ---- ---- The cjson module will throw an error during JSON conversion if any invalid ---- data is encountered. Refer to `cjson.encode` and cjson.decode for details. ---- ---- The cjson.safe module behaves identically to the cjson module, except when ---- errors are encountered during JSON conversion. On error, the cjson_safe.encode ---- and cjson_safe.decode functions will return nil followed by the error message. ---- ---- cjson.new can be used to instantiate an independent copy of the Lua CJSON ---- module. The new module has a separate persistent encoding buffer, and default settings. ---- ---- Lua CJSON can support Lua implementations using multiple preemptive threads ---- within a single Lua state provided the persistent encoding buffer is not ---- shared. This can be achieved by one of the following methods: ---- ---- * Disabling the persistent encoding buffer with cjson.encode_keep_buffer ---- * Ensuring each thread calls cjson.encode separately (ie, treat cjson.encode as non-reentrant). ---- * Using a separate cjson module table per preemptive thread (cjson.new) ---- ---- ## Note ---- ---- Lua CJSON uses `strtod` and `snprintf` to perform numeric conversion as they ---- are usually well supported, fast and bug free. However, these functions ---- require a workaround for JSON encoding/parsing under locales using a comma ---- decimal separator. Lua CJSON detects the current locale during instantiation ---- to determine and automatically implement the workaround if required. Lua ---- CJSON should be reinitialised via cjson.new if the locale of the current ---- process changes. Using a different locale per thread is not supported. ---- ----@class cjson ----@field _NAME string ----@field _VERSION string ----@field null cjson.null ----@field empty_array cjson.empty_array ----@field array_mt cjson.array_mt ----@field empty_array_mt cjson.empty_array_mt ---- -local cjson = {} - - ---- A metatable which can "tag" a table as a JSON Array in case it is empty (that ---- is, if the table has no elements, `cjson.encode()` will encode it as an empty ---- JSON Array). ---- ---- Instead of: ---- ----```lua ---- local function serialize(arr) ---- if #arr < 1 then ---- arr = cjson.empty_array ---- end ---- ---- return cjson.encode({some_array = arr}) ---- end ----``` ---- ---- This is more concise: ---- ----```lua ---- local function serialize(arr) ---- setmetatable(arr, cjson.empty_array_mt) ---- ---- return cjson.encode({some_array = arr}) ---- end ----``` ---- ---- Both will generate: ---- ----```json ---- { ---- "some_array": [] ---- } ----``` ---- ---- **NOTE:** This field is specific to the OpenResty cjson fork. ---- ----@alias cjson.empty_array_mt table - - ---- When lua-cjson encodes a table with this metatable, it will systematically ---- encode it as a JSON Array. The resulting, encoded Array will contain the ---- array part of the table, and will be of the same length as the `#` operator ---- on that table. Holes in the table will be encoded with the null JSON value. ---- ---- ## Example ---- ----```lua ---- local t = { "hello", "world" } ---- setmetatable(t, cjson.array_mt) ---- cjson.encode(t) -- ["hello","world"] ----``` ---- ---- Or: ---- ----```lua ---- local t = {} ---- t[1] = "one" ---- t[2] = "two" ---- t[4] = "three" ---- t.foo = "bar" ---- setmetatable(t, cjson.array_mt) ---- cjson.encode(t) -- ["one","two",null,"three"] ----``` ---- ---- **NOTE:** This field is specific to the OpenResty cjson fork. ---- ---- This value was introduced in the 2.1.0.5 release of this module. ---- ----@alias cjson.array_mt table - - ---- Sentinel type that denotes a JSON `null` value ----@alias cjson.null lightuserdata - - ---- A lightuserdata, similar to `cjson.null`, which will be encoded as an empty ---- JSON Array by `cjson.encode()`. ---- ---- For example, since encode_empty_table_as_object is true by default: ---- ----```lua ---- local cjson = require "cjson" ---- ---- local json = cjson.encode({ ---- foo = "bar", ---- some_object = {}, ---- some_array = cjson.empty_array ---- }) ----``` ---- ---- This will generate: ---- ----```json ---- { ---- "foo": "bar", ---- "some_object": {}, ---- "some_array": [] ---- } ----``` ---- ---- **NOTE:** This field is specific to the OpenResty cjson fork. ---- ----@alias cjson.empty_array lightuserdata - - ---- unserialize a json string to a lua value ---- ---- `cjson.decode` will deserialise any UTF-9 JSON string into a Lua value or table. ---- ---- UTF-16 and UTF-32 JSON strings are not supported. ---- ---- cjson.decode requires that any NULL (ASCII 0) and double quote (ASCII 34) characters are escaped within strings. All escape codes will be decoded and other bytes will be passed transparently. UTF-8 characters are not validated during decoding and should be checked elsewhere if required. ---- ---- JSON null will be converted to a NULL lightuserdata value. This can be compared with `cjson.null` for convenience. ---- ---- By default, numbers incompatible with the JSON specification (infinity, NaN, hexadecimal) can be decoded. This default can be changed with `cjson.decode_invalid_numbers()`. ---- ---- ```lua ---- local json_text '[ true, { "foo": "bar" } ]' ---- cjson.decode(json_text) --> { true, { foo = "bar" } } ---- ``` ---- ----@param json string ----@return any -function cjson.decode(json) end - - ---- decode_invalid_numbers ---- ---- Lua CJSON may generate an error when trying to decode numbers not supported ---- by the JSON specification. Invalid numbers are defined as: ---- ---- * infinity ---- * not-a-number (NaN) ---- * hexadecimal ---- ---- Available settings: ---- ---- * `true`: Accept and decode invalid numbers. This is the default setting. ---- * `false`: Throw an error when invalid numbers are encountered. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ----@param setting? boolean # (default: `true`) ----@return boolean setting # the value of the current setting -function cjson.decode_invalid_numbers(setting) end - - ---- decode_max_depth ---- ---- Lua CJSON will generate an error when parsing deeply nested JSON once the ---- maximum array/object depth has been exceeded. This check prevents ---- unnecessarily complicated JSON from slowing down the application, or crashing ---- the application due to lack of process stack space. ---- ---- An error may be generated before the depth limit is hit if Lua is unable to ---- allocate more objects on the Lua stack. ---- ---- By default, Lua CJSON will reject JSON with arrays and/or objects nested ---- more than 1000 levels deep. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ----@param depth? integer # must be positive (default `1000`) ----@return integer depth -function cjson.decode_max_depth(depth) end - - ---- decode_array_with_array_mt ---- ---- If enabled, JSON Arrays decoded by cjson.decode will result in Lua tables ---- with the array_mt metatable. This can ensure a 1-to-1 relationship between ---- arrays upon multiple encoding/decoding of your JSON data with this module. ---- ---- If disabled, JSON Arrays will be decoded to plain Lua tables, without the ---- `cjson.array_mt` metatable. ---- ---- ## Example ---- ----```lua ---- local cjson = require "cjson" ---- ---- -- default behavior ---- local my_json = [[{"my_array":[]}]] ---- local t = cjson.decode(my_json) ---- cjson.encode(t) -- {"my_array":{}} back to an object ---- ---- -- now, if this behavior is enabled ---- cjson.decode_array_with_array_mt(true) ---- ---- local my_json = [[{"my_array":[]}]] ---- local t = cjson.decode(my_json) ---- cjson.encode(t) -- {"my_array":[]} properly re-encoded as an array ----``` ---- ---- **NOTE:** This function is specific to the OpenResty cjson fork. ---- ----@param enabled boolean # (default: false) -function cjson.decode_array_with_array_mt(enabled) end - - ---- serialize a lua value to a json string ---- ---- cjson.encode will serialise a Lua value into a string containing the JSON representation. ---- ---- cjson.encode supports the following types: ---- ---- * boolean ---- * lightuserdata (NULL value only) ---- * nil ---- * number ---- * string ---- * table ---- ---- The remaining Lua types will generate an error: ---- ---- * function ---- * lightuserdata (non-NULL values) ---- * thread ---- * userdata ---- ---- By default, numbers are encoded with 14 significant digits. Refer to cjson.encode_number_precision for details. ---- ---- Lua CJSON will escape the following characters within each UTF-8 string: ---- ---- * Control characters (ASCII 0 - 31) ---- * Double quote (ASCII 34) ---- * Forward slash (ASCII 47) ---- * Blackslash (ASCII 92) ---- * Delete (ASCII 127) ---- ---- All other bytes are passed transparently. ---- ---- ---- ## Caution ---- ---- Lua CJSON will successfully encode/decode binary strings, but this is technically not supported by JSON and may not be compatible with other JSON libraries. To ensure the output is valid JSON, applications should ensure all Lua strings passed to cjson.encode are UTF-8. ---- ---- --- ---- Base64 is commonly used to encode binary data as the most efficient encoding under UTF-8 can only reduce the encoded size by a further ~8%. Lua Base64 routines can be found in the LuaSocket and lbase64 packages. ---- ---- Lua CJSON uses a heuristic to determine whether to encode a Lua table as a JSON array or an object. A Lua table with only positive integer keys of type number will be encoded as a JSON array. All other tables will be encoded as a JSON object. ---- ---- Lua CJSON does not use metamethods when serialising tables. ---- ---- * `rawget()` is used to iterate over Lua arrays ---- * `next()` is used to iterate over Lua objects ---- ---- Lua arrays with missing entries (sparse arrays) may optionally be encoded in ---- several different ways. Refer to cjson.encode_sparse_array for details. ---- ---- JSON object keys are always strings. Hence cjson.encode only supports table ---- keys which are type number or string. All other types will generate an error. ---- ---- ## Note ---- Standards compliant JSON must be encapsulated in either an object ({}) or an array ([]). If strictly standards compliant JSON is desired, a table must be passed to cjson.encode. ---- ---- --- ---- ---- By default, encoding the following Lua values will generate errors: ---- ---- * Numbers incompatible with the JSON specification (infinity, NaN) ---- * Tables nested more than 1000 levels deep ---- * Excessively sparse Lua arrays ---- ---- These defaults can be changed with: ---- ---- * cjson.encode_invalid_numbers ---- * cjson.encode_max_depth ---- * cjson.encode_sparse_array ---- ----```lua ---- local value = { true, { foo = "bar" } } ---- cjson.encode(value) --> '[true,{"foo":"bar"}]' ----``` ---- ----@param value any ----@return string json -function cjson.encode(value) end - - ---- encode_invalid_numbers ---- ---- Lua CJSON may generate an error when encoding floating point numbers not ---- supported by the JSON specification (invalid numbers): ---- ---- * infinity ---- * not-a-number (NaN) ---- ---- Available settings: ---- ---- * `true`: Allow invalid numbers to be encoded. This will generate non-standard JSON, but this output is supported by some libraries. ---- * `false`: Throw an error when attempting to encode invalid numbers. This is the default setting. ---- * `"null"`: Encode invalid numbers as a JSON null value. This allows infinity and NaN to be encoded into valid JSON. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ----@param setting? boolean|'"null"' ----@return boolean|'"null"' setting -function cjson.encode_invalid_numbers(setting) end - - ---- encode_keep_buffer ---- ---- Lua CJSON can reuse the JSON encoding buffer to improve performance. ---- ---- Available settings: ---- ---- * `true` - The buffer will grow to the largest size required and is not freed until the Lua CJSON module is garbage collected. This is the default setting. ---- * `false` - Free the encode buffer after each call to cjson.encode. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ----@param setting? boolean ----@return boolean setting -function cjson.encode_keep_buffer(setting) end - - ---- encode_max_depth ---- ---- Once the maximum table depth has been exceeded Lua CJSON will generate an ---- error. This prevents a deeply nested or recursive data structure from ---- crashing the application. ---- ---- By default, Lua CJSON will generate an error when trying to encode data ---- structures with more than 1000 nested tables. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ----@param depth? integer # must be positive (default `1000`) ----@return integer depth -function cjson.encode_max_depth(depth) end - - ---- encode_number_precision ---- ---- The amount of significant digits returned by Lua CJSON when encoding numbers ---- can be changed to balance accuracy versus performance. For data structures ---- containing many numbers, setting cjson.encode_number_precision to a smaller ---- integer, for example 3, can improve encoding performance by up to 50%. ---- ---- By default, Lua CJSON will output 14 significant digits when converting a number to text. ---- ---- The current setting is always returned, and is only updated when an argument is provided. ---- ---- **NOTE:** The maximum value of 16 is only supported by the OpenResty cjson ---- fork. The maximum in the upstream mpx/lua-cjson module is 14. ---- ----@param precision? integer # must be between 1 and 16 ----@return integer precision -function cjson.encode_number_precision(precision) end - - ---- encode_sparse_array ---- ---- Lua CJSON classifies a Lua table into one of three kinds when encoding a JSON array. This is determined by the number of values missing from the Lua array as follows: ---- ---- * Normal - All values are available. ---- * Sparse - At least 1 value is missing. ---- * Excessively sparse - The number of values missing exceeds the configured ratio. ---- ---- Lua CJSON encodes sparse Lua arrays as JSON arrays using JSON null for the missing entries. ---- ---- An array is excessively sparse when all the following conditions are met: ---- ---- * ratio > 0 ---- * maximum_index > safe ---- * maximum_index > item_count * ratio ---- ---- Lua CJSON will never consider an array to be excessively sparse when ratio = 0. ---- The safe limit ensures that small Lua arrays are always encoded as sparse arrays. ---- ---- By default, attempting to encode an excessively sparse array will generate ---- an error. If convert is set to true, excessively sparse arrays will be ---- converted to a JSON object. ---- ---- The current settings are always returned. A particular setting is only ---- changed when the argument is provided (non-nil). ---- ---- ## Example: Encoding a sparse array ---- ----```lua ---- cjson.encode({ [3] = "data" }) ---- -- Returns: '[null,null,"data"]' ----``` ---- ---- ## Example: Enabling conversion to a JSON object ---- ----```lua ---- cjson.encode_sparse_array(true) ---- cjson.encode({ [1000] = "excessively sparse" }) ---- -- Returns: '{"1000":"excessively sparse"}' ----``` ---- ----@param convert? boolean # (default: false) ----@param ratio? integer # must be positive (default: 2) ----@param safe? integer # must be positive (default: 10) ----@return boolean convert ----@return integer ratio ----@return integer safe -function cjson.encode_sparse_array(convert, ratio, safe) end - - ---- encode_empty_table_as_object ---- ---- Change the default behavior when encoding an empty Lua table. ---- ---- By default, empty Lua tables are encoded as empty JSON Objects (`{}`). If ---- this is set to false, empty Lua tables will be encoded as empty JSON Arrays ---- instead (`[]`). ---- ---- **NOTE:** This function is specific to the OpenResty cjson fork. ---- ----@param setting boolean|'"on"'|'"off"' -function cjson.encode_empty_table_as_object(setting) end - - ---- encode_escape_forward_slash ---- ---- If enabled, forward slash '/' will be encoded as '\/'. ---- ---- If disabled, forward slash '/' will be encoded as '/' (no escape is applied). ---- ---- **NOTE:** This function is specific to the OpenResty cjson fork. ---- ----@param enabled boolean # (default: true) -function cjson.encode_escape_forward_slash(enabled) end - - ---- instantiate an independent copy of the Lua CJSON module ---- ---- The new module has a separate persistent encoding buffer, and default settings ----@return cjson -function cjson.new() end - - -return cjson diff --git a/meta/3rd/OpenResty/library/cjson.safe.lua b/meta/3rd/OpenResty/library/cjson.safe.lua deleted file mode 100644 index 64244b150..000000000 --- a/meta/3rd/OpenResty/library/cjson.safe.lua +++ /dev/null @@ -1,46 +0,0 @@ ----@meta - ---- The `cjson.safe` module behaves identically to the `cjson` module, except when ---- errors are encountered during JSON conversion. On error, the `cjson.safe.encode` ---- and `cjson.safe.decode` functions will return `nil` followed by the error message. ---- ---- @see cjson ---- ----@class cjson.safe : cjson -local cjson_safe = {} - - ---- unserialize a json string to a lua value ---- ---- Returns `nil` and an error string when the input cannot be decoded. ---- ---- ```lua ---- local value, err = cjson_safe.decode(some_json_string) ---- ``` ----@param json string ----@return any? decoded ----@return string? error -function cjson_safe.decode(json) end - - ---- serialize a lua value to a json string ---- ---- Returns `nil` and an error string when the input cannot be encoded. ---- ---- ```lua ---- local json, err = cjson_safe.encode(some_lua_value) ---- ``` ----@param value any ----@return string? encoded ----@return string? error -function cjson_safe.encode(value) end - - ---- instantiate an independent copy of the Lua CJSON module ---- ---- The new module has a separate persistent encoding buffer, and default settings ----@return cjson.safe -function cjson_safe.new() end - - -return cjson_safe diff --git a/meta/3rd/bee b/meta/3rd/bee new file mode 160000 index 000000000..c0bc5349e --- /dev/null +++ b/meta/3rd/bee @@ -0,0 +1 @@ +Subproject commit c0bc5349e5fa741d7ad8d6d5c7624a54dcfd9ebe diff --git a/meta/3rd/busted b/meta/3rd/busted new file mode 160000 index 000000000..5ed85d0e0 --- /dev/null +++ b/meta/3rd/busted @@ -0,0 +1 @@ +Subproject commit 5ed85d0e016a5eb5eca097aa52905eedf1b180f1 diff --git a/meta/3rd/example/config.json b/meta/3rd/example/config.json new file mode 100644 index 000000000..79170468f --- /dev/null +++ b/meta/3rd/example/config.json @@ -0,0 +1,22 @@ +{ + // if not set, the folder name will be used + "name" : "Example", + // list of matched words + "words" : ["thisIsAnExampleWord%.ifItExistsInFile%.thenTryLoadThisLibrary"], + // list or matched file names. `.lua`, `.dll` and `.so` only + "files" : ["thisIsAnExampleFile%.ifItExistsInWorkSpace%.thenTryLoadThisLibrary%.lua"], + // lsit of settings to be changed + "settings" : { + "Lua.runtime.version" : "LuaJIT", + "Lua.diagnostics.globals" : [ + "global1", + "global2" + ], + "Lua.runtime.special" : { + "include" : "require" + }, + "Lua.runtime.builtin" : { + "io" : "disable" + } + } +} diff --git a/meta/3rd/example/config.lua b/meta/3rd/example/config.lua deleted file mode 100644 index e3d13e418..000000000 --- a/meta/3rd/example/config.lua +++ /dev/null @@ -1,38 +0,0 @@ --- if not set, the folder name will be used -name = 'Example' --- list of matched words -words = {'thisIsAnExampleWord%.ifItExistsInFile%.thenTryLoadThisLibrary'} --- list or matched file names. `.lua`, `.dll` and `.so` only -files = {'thisIsAnExampleFile%.ifItExistsInWorkSpace%.thenTryLoadThisLibrary%.lua'} --- lsit of settings to be changed -configs = { - { - key = 'Lua.runtime.version', - action = 'set', - value = 'LuaJIT', - }, - { - key = 'Lua.diagnostics.globals', - action = 'add', - value = 'global1', - }, - { - key = 'Lua.runtime.special', - action = 'prop', - prop = 'include', - value = 'require', - }, - { - key = 'Lua.runtime.builtin', - action = 'prop', - prop = 'io', - value = 'disable', - }, -} -for _, name in ipairs {'global2', 'global3', 'global4'} do - configs[#configs+1] = { - key = 'Lua.diagnostics.globals', - action = 'add', - value = name, - } -end diff --git a/meta/3rd/example/plugin.lua b/meta/3rd/example/plugin.lua index 011620427..7224f9149 100644 --- a/meta/3rd/example/plugin.lua +++ b/meta/3rd/example/plugin.lua @@ -1,5 +1,5 @@ -- if this file exists, then change setting `Lua.runtime.plugin` --- see https://github.com/sumneko/lua-language-server/wiki/Plugins +-- see https://luals.github.io/wiki/plugins function OnSetText(uri, text) local diffs = {} diff --git a/meta/3rd/ffi-reflect b/meta/3rd/ffi-reflect new file mode 160000 index 000000000..e9037efca --- /dev/null +++ b/meta/3rd/ffi-reflect @@ -0,0 +1 @@ +Subproject commit e9037efca4021a15552b281f5e91418afd370d8f diff --git a/meta/3rd/lfs b/meta/3rd/lfs new file mode 160000 index 000000000..9b5cfc15b --- /dev/null +++ b/meta/3rd/lfs @@ -0,0 +1 @@ +Subproject commit 9b5cfc15be744c829c66519cb11e49669ae7e39b diff --git a/meta/3rd/lfs/config.lua b/meta/3rd/lfs/config.lua deleted file mode 100644 index b94f490db..000000000 --- a/meta/3rd/lfs/config.lua +++ /dev/null @@ -1,9 +0,0 @@ -name = 'luafilesystem' -words = { 'lfs%.%w+' } -configs = { - { - key = 'Lua.diagnostics.globals', - action = 'add', - value = 'lfs', - }, -} diff --git a/meta/3rd/lfs/library/lfs.lua b/meta/3rd/lfs/library/lfs.lua deleted file mode 100644 index 76f457b79..000000000 --- a/meta/3rd/lfs/library/lfs.lua +++ /dev/null @@ -1,159 +0,0 @@ ----@meta - ----@class LuaFileSystem.Attributes ----@field [LuaFileSystem.AttributeName] any ----@field ['mode'] LuaFileSystem.AttributeMode - ----@alias LuaFileSystem.AttributeMode ----|'file' ----|'directory' ----|'link' ----|'socket' ----|'char device' ----|"block device" ----|"named pipe" - ----@alias LuaFileSystem.AttributeName ----|'dev' -- on Unix systems, this represents the device that the inode resides on. On Windows systems, represents the drive number of the disk containing the file ----|'ino' -- on Unix systems, this represents the inode number. On Windows systems this has no meaning ----|'mode' -- string representing the associated protection mode (the values could be file, directory, link, socket, named pipe, char device, block device or other) ----|'nlink' -- number of hard links to the file ----|'uid' -- user-id of owner (Unix only, always 0 on Windows) ----|'gid' -- group-id of owner (Unix only, always 0 on Windows) ----|'rdev' -- on Unix systems, represents the device type, for special file inodes. On Windows systems represents the same as dev ----|'access' -- time of last access ----|'modification' -- time of last data modification ----|'change' -- time of last file status change ----|'size' -- file size, in bytes ----|'permissions' -- file permissions string ----|'blocks' -- block allocated for file; (Unix only) ----|'blksize' -- optimal file system I/O blocksize; (Unix only) - ----@class LuaFileSystem -local lfs = {} - ---[[ -Returns a table with the file attributes corresponding to filepath (or nil followed by an error message and a system-dependent error code in case of error). If the second optional argument is given and is a string, then only the value of the named attribute is returned (this use is equivalent to lfs.attributes(filepath)[request_name], but the table is not created and only one attribute is retrieved from the O.S.). if a table is passed as the second argument, it (result_table) is filled with attributes and returned instead of a new table. The attributes are described as follows; attribute mode is a string, all the others are numbers, and the time related attributes use the same time reference of os.time: -]] ----@overload fun(filepath:string):LuaFileSystem.Attributes ----@overload fun(filepath:string, result_table:LuaFileSystem.Attributes) ----@param filepath string ----@param request_name LuaFileSystem.AttributeName ----@return string|integer|LuaFileSystem.AttributeMode -function lfs.attributes(filepath, request_name) -end - ---[[ -Changes the current working directory to the given path. -Returns true in case of success or nil plus an error string. -]] ----@param path string ----@return boolean, string -function lfs.chdir(path) -end - ---[[ -Creates a lockfile (called lockfile.lfs) in path if it does not exist and returns the lock. If the lock already exists checks if it's stale, using the second parameter (default for the second parameter is INT_MAX, which in practice means the lock will never be stale. To free the the lock call lock:free(). -In case of any errors it returns nil and the error message. In particular, if the lock exists and is not stale it returns the "File exists" message. -]] ----@param path string ----@param seconds_stale? number ----@return boolean, string -function lfs.lock_dir(path, seconds_stale) -end - ----Returns a string with the current working directory or nil plus an error string. ----@return string -function lfs.currentdir() -end - ---[[ -Lua iterator over the entries of a given directory. Each time the iterator is called with dir_obj it returns a directory entry's name as a string, or nil if there are no more entries. You can also iterate by calling dir_obj:next(), and explicitly close the directory before the iteration finished with dir_obj:close(). Raises an error if path is not a directory. -]] ----@param path string ----@return fun():string -function lfs.dir(path) -end - ---[[ -Locks a file or a part of it. This function works on open files; the file handle should be specified as the first argument. The string mode could be either r (for a read/shared lock) or w (for a write/exclusive lock). The optional arguments start and length can be used to specify a starting point and its length; both should be numbers. -Returns true if the operation was successful; in case of error, it returns nil plus an error string. -]] ----@param filehandle file* ----@param mode openmode ----@param start? integer ----@param length? integer ----@return boolean, string -function lfs.lock(filehandle, mode, start, length) -end - ---[[ - Creates a link. The first argument is the object to link to and the second is the name of the link. If the optional third argument is true, the link will by a symbolic link (by default, a hard link is created). -]] ----@param old string ----@param new string ----@param symlink? boolean ----@return boolean, string -function lfs.link(old, new, symlink) - -end - ---[[ - Creates a new directory. The argument is the name of the new directory. - Returns true in case of success or nil, an error message and a system-dependent error code in case of error. -]] ----@param dirname string ----@return boolean, string -function lfs.mkdir(dirname) -end - ---[[ - Removes an existing directory. The argument is the name of the directory. - Returns true in case of success or nil, an error message and a system-dependent error code in case of error. -]] ----@param dirname string ----@return boolean, string -function lfs.rmdir(dirname) -end - ---[[ -Sets the writing mode for a file. The mode string can be either "binary" or "text". Returns true followed the previous mode string for the file, or nil followed by an error string in case of errors. On non-Windows platforms, where the two modes are identical, setting the mode has no effect, and the mode is always returned as binary. -]] ----@param file string ----@param mode 'binary'|'text' ----@return boolean, string -function lfs.setmode(file, mode) -end - ---[[ -Identical to lfs.attributes except that it obtains information about the link itself (not the file it refers to). It also adds a target field, containing the file name that the symlink points to. On Windows this function does not yet support links, and is identical to lfs.attributes. -]] ----@param filepath string ----@param request_name? LuaFileSystem.AttributeName ----@return LuaFileSystem.Attributes -function lfs.symlinkattributes(filepath, request_name) -end - ---[[ -Set access and modification times of a file. This function is a bind to utime function. The first argument is the filename, the second argument (atime) is the access time, and the third argument (mtime) is the modification time. Both times are provided in seconds (which should be generated with Lua standard function os.time). If the modification time is omitted, the access time provided is used; if both times are omitted, the current time is used. -Returns true in case of success or nil, an error message and a system-dependent error code in case of error. -]] ----@param filepath string ----@param atime? integer ----@param mtime? integer ----@return boolean, string -function lfs.touch(filepath, atime, mtime) -end - ---[[ -Unlocks a file or a part of it. This function works on open files; the file handle should be specified as the first argument. The optional arguments start and length can be used to specify a starting point and its length; both should be numbers. -Returns true if the operation was successful; in case of error, it returns nil plus an error string. -]] ----@param filehandle file* ----@param start? integer ----@param length? integer ----@return boolean, string -function lfs.unlock(filehandle, start, length) -end - -return lfs diff --git a/meta/3rd/love2d b/meta/3rd/love2d new file mode 160000 index 000000000..dad72a7ea --- /dev/null +++ b/meta/3rd/love2d @@ -0,0 +1 @@ +Subproject commit dad72a7eae31f35bf4c6529e5b81f6187b5b7377 diff --git a/meta/3rd/lovr b/meta/3rd/lovr new file mode 160000 index 000000000..3ba215f98 --- /dev/null +++ b/meta/3rd/lovr @@ -0,0 +1 @@ +Subproject commit 3ba215f98e1647111b50b311d45c31338fdc615f diff --git a/meta/3rd/luaecs b/meta/3rd/luaecs new file mode 160000 index 000000000..21192fbdc --- /dev/null +++ b/meta/3rd/luaecs @@ -0,0 +1 @@ +Subproject commit 21192fbdccc0f140dae2f74f7c3ec7d74e2aadd0 diff --git a/meta/3rd/luassert b/meta/3rd/luassert new file mode 160000 index 000000000..d3528bb67 --- /dev/null +++ b/meta/3rd/luassert @@ -0,0 +1 @@ +Subproject commit d3528bb679302cbfdedefabb37064515ab95f7b9 diff --git a/meta/3rd/luv b/meta/3rd/luv new file mode 160000 index 000000000..3615eb12c --- /dev/null +++ b/meta/3rd/luv @@ -0,0 +1 @@ +Subproject commit 3615eb12c94a7cfa7184b8488cf908abb5e94c9c diff --git a/meta/3rd/skynet b/meta/3rd/skynet new file mode 160000 index 000000000..afa6717ac --- /dev/null +++ b/meta/3rd/skynet @@ -0,0 +1 @@ +Subproject commit afa6717ac4f71e42012fa2ebf4b410d04db647ae diff --git a/meta/spell/lua_dict.txt b/meta/spell/lua_dict.txt index 29f5522a4..59b35a8c0 100644 --- a/meta/spell/lua_dict.txt +++ b/meta/spell/lua_dict.txt @@ -219,6 +219,12 @@ formatter env math suc -utf -ansi - +const +param +params +func +funcs +enum +enums +substring +globals diff --git a/meta/template/basic.lua b/meta/template/basic.lua index 552aa9dba..4a9360fd0 100644 --- a/meta/template/basic.lua +++ b/meta/template/basic.lua @@ -1,4 +1,4 @@ ----@meta +---@meta _ ---#DES 'arg' ---@type string[] @@ -8,27 +8,30 @@ arg = {} ---@generic T ---@param v? T ---@param message? any +---@param ... any ---@return T -function assert(v, message) end +---@return any ... +function assert(v, message, ...) end ---@alias gcoptions ----|>'"collect"' # ---#DESTAIL 'cgopt.collect' ----| '"stop"' # ---#DESTAIL 'cgopt.stop' ----| '"restart"' # ---#DESTAIL 'cgopt.restart' ----| '"count"' # ---#DESTAIL 'cgopt.count' ----| '"step"' # ---#DESTAIL 'cgopt.step' ----| '"isrunning"' # ---#DESTAIL 'cgopt.isrunning' +---|>"collect" # ---#DESTAIL 'cgopt.collect' +---| "stop" # ---#DESTAIL 'cgopt.stop' +---| "restart" # ---#DESTAIL 'cgopt.restart' +---| "count" # ---#DESTAIL 'cgopt.count' +---| "step" # ---#DESTAIL 'cgopt.step' +---| "isrunning" # ---#DESTAIL 'cgopt.isrunning' ---#if VERSION >= 5.4 then ----| '"incremental"' # ---#DESTAIL 'cgopt.incremental' ----| '"generational"' # ---#DESTAIL 'cgopt.generational' +---| "incremental" # ---#DESTAIL 'cgopt.incremental' +---| "generational" # ---#DESTAIL 'cgopt.generational' ---#else ----| '"setpause"' # ---#DESTAIL 'cgopt.setpause' ----| '"setstepmul"' # ---#DESTAIL 'cgopt.setstepmul' +---| "setpause" # ---#DESTAIL 'cgopt.setpause' +---| "setstepmul" # ---#DESTAIL 'cgopt.setstepmul' ---#end ---#if VERSION >= 5.4 then ---#DES 'collectgarbage' ---@param opt? gcoptions +---@param ... any ---@return any function collectgarbage(opt, ...) end ---#else @@ -55,7 +58,7 @@ _G = {} ---@version 5.1 ---#DES 'getfenv' ----@param f? integer|async fun() +---@param f? integer|async fun(...):... ---@return table ---@nodiscard function getfenv(f) end @@ -75,9 +78,9 @@ function getmetatable(object) end function ipairs(t) end ---@alias loadmode ----| '"b"' # ---#DESTAIL 'loadmode.b' ----| '"t"' # ---#DESTAIL 'loadmode.t' ----|>'"bt"' # ---#DESTAIL 'loadmode.bt' +---| "b" # ---#DESTAIL 'loadmode.b' +---| "t" # ---#DESTAIL 'loadmode.t' +---|>"bt" # ---#DESTAIL 'loadmode.bt' ---#if VERSION <= 5.1 and not JIT then ---#DES 'load<5.1' @@ -127,7 +130,7 @@ function loadfile(filename, mode, env) end function loadstring(text, chunkname) end ---@version 5.1 ----@param proxy boolean|table +---@param proxy boolean|table|userdata ---@return userdata ---@nodiscard function newproxy(proxy) end @@ -135,6 +138,7 @@ function newproxy(proxy) end ---@version 5.1 ---#DES 'module' ---@param name string +---@param ... any function module(name, ...) end ---#DES 'next' @@ -157,15 +161,17 @@ function pairs(t) end ---#if VERSION == 5.1 and not JIT then ---@param f function ---#else ----@param f async fun() +---@param f async fun(...):... ---#end ---@param arg1? any +---@param ... any ---@return boolean success ---@return any result ----@return ... +---@return any ... function pcall(f, arg1, ...) end ---#DES 'print' +---@param ... any function print(...) end ---#DES 'rawequal' @@ -196,21 +202,62 @@ function rawlen(v) end function rawset(table, index, value) end ---#DES 'select' ----@param index integer|'"#"' +---@param index integer|"#" +---@param ... any ---@return any ---@nodiscard function select(index, ...) end ---@version 5.1 ---#DES 'setfenv' ----@param f async fun()|integer +---@param f async fun(...):...|integer ---@param table table ---@return function function setfenv(f, table) end + +---@class metatable +---@field __mode 'v'|'k'|'kv'|nil +---@field __metatable any|nil +---@field __tostring (fun(t):string)|nil +---@field __gc fun(t)|nil +---@field __add (fun(t1,t2):any)|nil +---@field __sub (fun(t1,t2):any)|nil +---@field __mul (fun(t1,t2):any)|nil +---@field __div (fun(t1,t2):any)|nil +---@field __mod (fun(t1,t2):any)|nil +---@field __pow (fun(t1,t2):any)|nil +---@field __unm (fun(t):any)|nil +---#if VERSION >= 5.3 then +---@field __idiv (fun(t1,t2):any)|nil +---@field __band (fun(t1,t2):any)|nil +---@field __bor (fun(t1,t2):any)|nil +---@field __bxor (fun(t1,t2):any)|nil +---@field __bnot (fun(t):any)|nil +---@field __shl (fun(t1,t2):any)|nil +---@field __shr (fun(t1,t2):any)|nil +---#end +---@field __concat (fun(t1,t2):any)|nil +---@field __len (fun(t):integer)|nil +---@field __eq (fun(t1,t2):boolean)|nil +---@field __lt (fun(t1,t2):boolean)|nil +---@field __le (fun(t1,t2):boolean)|nil +---@field __index table|(fun(t,k):any)|nil +---@field __newindex table|fun(t,k,v)|nil +---@field __call (fun(t,...):...)|nil +---#if VERSION > 5.1 or VERSION == JIT then +---@field __pairs (fun(t):(fun(t,k,v):any,any))|nil +---#end +---#if VERSION == JIT or VERSION == 5.2 then +---@field __ipairs (fun(t):(fun(t,k,v):(integer|nil),any))|nil +---#end +---#if VERSION >= 5.4 then +---@field __close (fun(t,errobj):any)|nil +---#end + ---#DES 'setmetatable' ---@param table table ----@param metatable? table +---@param metatable? metatable|table ---@return table function setmetatable(table, metatable) end @@ -228,14 +275,17 @@ function tonumber(e) end function tostring(v) end ---@alias type ----| '"nil"' ----| '"number"' ----| '"string"' ----| '"boolean"' ----| '"table"' ----| '"function"' ----| '"thread"' ----| '"userdata"' +---| "nil" +---| "number" +---| "string" +---| "boolean" +---| "table" +---| "function" +---| "thread" +---| "userdata" +---#if VERSION == JIT then +---| "cdata" +---#end ---#DES 'type' ---@param v any @@ -245,18 +295,19 @@ function type(v) end ---#DES '_VERSION' ---#if VERSION == 5.1 then -_VERSION = 'Lua 5.1' +_VERSION = "Lua 5.1" ---#elseif VERSION == 5.2 then -_VERSION = 'Lua 5.2' +_VERSION = "Lua 5.2" ---#elseif VERSION == 5.3 then -_VERSION = 'Lua 5.3' +_VERSION = "Lua 5.3" ---#elseif VERSION == 5.4 then -_VERSION = 'Lua 5.4' +_VERSION = "Lua 5.4" ---#end ---@version >5.4 ---#DES 'warn' ---@param message string +---@param ... any function warn(message, ...) end ---#if VERSION == 5.1 and not JIT then @@ -265,16 +316,17 @@ function warn(message, ...) end ---@param err function ---@return boolean success ---@return any result ----@return ... +---@return any ... function xpcall(f, err) end ---#else ---#DES 'xpcall>5.2' ----@param f async fun() +---@param f async fun(...):... ---@param msgh function ---@param arg1? any +---@param ... any ---@return boolean success ---@return any result ----@return ... +---@return any ... function xpcall(f, msgh, arg1, ...) end ---#end diff --git a/meta/template/bit.lua b/meta/template/bit.lua index 369c5f9d1..e30d8c912 100644 --- a/meta/template/bit.lua +++ b/meta/template/bit.lua @@ -1,5 +1,5 @@ ---#if not JIT then DISABLE() end ----@meta +---@meta bit ---@version JIT ---@class bitlib @@ -12,7 +12,7 @@ function bit.tobit(x) end ---@param x integer ---@param n? integer ----@return integer y +---@return string y ---@nodiscard function bit.tohex(x, n) end diff --git a/meta/template/bit32.lua b/meta/template/bit32.lua index bcb39c669..ddccbab49 100644 --- a/meta/template/bit32.lua +++ b/meta/template/bit32.lua @@ -1,5 +1,5 @@ ---#if VERSION ~= 5.2 then DISABLE() end ----@meta +---@meta bit32 ---@version 5.2 ---#DES 'bit32' diff --git a/meta/template/builtin.lua b/meta/template/builtin.lua index 41858096f..466a0966e 100644 --- a/meta/template/builtin.lua +++ b/meta/template/builtin.lua @@ -1,4 +1,4 @@ ----@meta +---@meta _ ---@class unknown ---@class any diff --git a/meta/template/coroutine.lua b/meta/template/coroutine.lua index 0b07bf5cb..91a3aba3f 100644 --- a/meta/template/coroutine.lua +++ b/meta/template/coroutine.lua @@ -1,11 +1,11 @@ ----@meta +---@meta coroutine ---#DES 'coroutine' ---@class coroutinelib coroutine = {} ---#DES 'coroutine.create' ----@param f async fun() +---@param f async fun(...):... ---@return thread ---@nodiscard function coroutine.create(f) end @@ -17,6 +17,7 @@ function coroutine.create(f) end ---@nodiscard function coroutine.isyieldable(co) end ---#else +---@version >5.2 ---#DES 'coroutine.isyieldable' ---@return boolean ---@nodiscard @@ -34,8 +35,7 @@ function coroutine.close(co) end ---@param co thread ---@param val1? any ---@return boolean success ----@return any result ----@return ... +---@return any ... function coroutine.resume(co, val1, ...) end ---#DES 'coroutine.running' @@ -55,14 +55,14 @@ function coroutine.running() end function coroutine.status(co) end ---#DES 'coroutine.wrap' ----@param f async fun() ----@return fun() +---@param f async fun(...):... +---@return fun(...):... ---@nodiscard function coroutine.wrap(f) end ---#DES 'coroutine.yield' ---@async ----@return ... +---@return any ... function coroutine.yield(...) end return coroutine diff --git a/meta/template/debug.lua b/meta/template/debug.lua index 7cb417b23..9f48ee98c 100644 --- a/meta/template/debug.lua +++ b/meta/template/debug.lua @@ -1,4 +1,4 @@ ----@meta +---@meta debug ---#DES 'debug' ---@class debuglib @@ -45,25 +45,25 @@ function debug.getfenv(o) end function debug.gethook(co) end ---@alias infowhat string ----|+'"n"' # ---#DESTAIL 'infowhat.n' ----|+'"S"' # ---#DESTAIL 'infowhat.S' ----|+'"l"' # ---#DESTAIL 'infowhat.l' ----|+'"t"' # ---#DESTAIL 'infowhat.t' +---|+"n" # ---#DESTAIL 'infowhat.n' +---|+"S" # ---#DESTAIL 'infowhat.S' +---|+"l" # ---#DESTAIL 'infowhat.l' +---|+"t" # ---#DESTAIL 'infowhat.t' ---#if VERSION <= 5.1 and not JIT then ----|+'"u"' # ---#DESTAIL 'infowhat.u<5.1' +---|+"u" # ---#DESTAIL 'infowhat.u<5.1' ---#else ----|+'"u"' # ---#DESTAIL 'infowhat.u>5.2' +---|+"u" # ---#DESTAIL 'infowhat.u>5.2' ---#end ----|+'"f"' # ---#DESTAIL 'infowhat.f' +---|+"f" # ---#DESTAIL 'infowhat.f' ---#if VERSION >= 5.4 then ----|+'"r"' # ---#DESTAIL 'infowhat.r' +---|+"r" # ---#DESTAIL 'infowhat.r' ---#end ----|+'"L"' # ---#DESTAIL 'infowhat.L' +---|+"L" # ---#DESTAIL 'infowhat.L' ---#DES 'debug.getinfo' ---@overload fun(f: integer|function, what?: infowhat):debuginfo ---@param thread thread ----@param f integer|async fun() +---@param f integer|async fun(...):... ---@param what? infowhat ---@return debuginfo ---@nodiscard @@ -81,9 +81,9 @@ function debug.getinfo(thread, f, what) end function debug.getlocal(thread, level, index) end ---#else ---#DES 'debug.getlocal>5.2' ----@overload fun(f: integer|async fun(), index: integer):string, any +---@overload fun(f: integer|async fun(...):..., index: integer):string, any ---@param thread thread ----@param f integer|async fun() +---@param f integer|async fun(...):... ---@param index integer ---@return string name ---@return any value @@ -103,7 +103,7 @@ function debug.getmetatable(object) end function debug.getregistry() end ---#DES 'debug.getupvalue' ----@param f async fun() +---@param f async fun(...):... ---@param up integer ---@return string name ---@return any value @@ -141,16 +141,16 @@ function debug.setcstacklimit(limit) end function debug.setfenv(object, env) end ---@alias hookmask string ----|+'"c"' # ---#DESTAIL 'hookmask.c' ----|+'"r"' # ---#DESTAIL 'hookmask.r' ----|+'"l"' # ---#DESTAIL 'hookmask.l' +---|+"c" # ---#DESTAIL 'hookmask.c' +---|+"r" # ---#DESTAIL 'hookmask.r' +---|+"l" # ---#DESTAIL 'hookmask.l' ---#DES 'debug.sethook' ----@overload fun(hook: async fun(), mask: hookmask, count?: integer) ----@overload fun(thread: thread) ----@overload fun() +---@overload fun(hook: (async fun(...):...), mask: hookmask, count?: integer) +---@overload fun(thread: thread):... +---@overload fun(...):... ---@param thread thread ----@param hook async fun() +---@param hook async fun(...):... ---@param mask hookmask ---@param count? integer function debug.sethook(thread, hook, mask, count) end @@ -172,7 +172,7 @@ function debug.setlocal(thread, level, index, value) end function debug.setmetatable(value, meta) end ---#DES 'debug.setupvalue' ----@param f async fun() +---@param f async fun(...):... ---@param up integer ---@param value any ---@return string name @@ -204,7 +204,7 @@ function debug.traceback(thread, message, level) end ---@version >5.2, JIT ---#DES 'debug.upvalueid' ----@param f async fun() +---@param f async fun(...):... ---@param n integer ---@return lightuserdata id ---@nodiscard @@ -212,9 +212,9 @@ function debug.upvalueid(f, n) end ---@version >5.2, JIT ---#DES 'debug.upvaluejoin' ----@param f1 async fun() +---@param f1 async fun(...):... ---@param n1 integer ----@param f2 async fun() +---@param f2 async fun(...):... ---@param n2 integer function debug.upvaluejoin(f1, n1, f2, n2) end diff --git a/meta/template/ffi.lua b/meta/template/ffi.lua index 833232d4b..a9d486578 100644 --- a/meta/template/ffi.lua +++ b/meta/template/ffi.lua @@ -1,14 +1,18 @@ ---#if not JIT then DISABLE() end ----@meta +---@meta ffi ---@class ffi.namespace*: table +---@field [string] function ----@class ffi.cdecl*: string ---@class ffi.ctype*: userdata +---@overload fun(init?: any, ...): ffi.cdata* +---@overload fun(nelem?: integer, init?: any, ...): ffi.cdata* local ctype + +---@class ffi.cdecl*: string ---@class ffi.cdata*: userdata ----@alias ffi.ct* ffi.cdecl*|ffi.ctype*|ffi.cdata* ----@class ffi.cb*: userdata +---@alias ffi.ct* ffi.ctype*|ffi.cdecl*|ffi.cdata* +---@class ffi.cb*: ffi.cdata* local cb ---@class ffi.VLA*: userdata ---@class ffi.VLS*: userdata @@ -20,8 +24,9 @@ local cb ---@field arch string local ffi = {} ----@param def string -function ffi.cdef(def) end +---@param def string +---@param params? any +function ffi.cdef(def, params, ...) end ---@param name string ---@param global? boolean @@ -29,6 +34,7 @@ function ffi.cdef(def) end ---@nodiscard function ffi.load(name, global) end +---@overload fun(ct: ffi.ct*, init: any, ...) ---@param ct ffi.ct* ---@param nelem? integer ---@param init? any @@ -36,19 +42,16 @@ function ffi.load(name, global) end ---@nodiscard function ffi.new(ct, nelem, init, ...) end ----@param nelem? integer ----@param init? any ----@return ffi.cdata* cdata -function ffi.ctype(nelem, init, ...) end - ----@param ct ffi.ct* +---@param ct ffi.ct* +---@param params? any ---@return ffi.ctype* ctype ---@nodiscard -function ffi.typeof(ct) end +function ffi.typeof(ct, params, ...) end ---@param ct ffi.ct* ---@param init any ---@return ffi.cdata* cdata +---@nodiscard function ffi.cast(ct, init) end ---@param ct ffi.ct* @@ -57,7 +60,7 @@ function ffi.cast(ct, init) end function ffi.metatype(ct, metatable) end ---@param cdata ffi.cdata* ----@param finalizer function +---@param finalizer? function ---@return ffi.cdata* cdata function ffi.gc(cdata, finalizer) end diff --git a/meta/template/io.lua b/meta/template/io.lua index ba1e9a02c..2200a6268 100644 --- a/meta/template/io.lua +++ b/meta/template/io.lua @@ -1,4 +1,4 @@ ----@meta +---@meta io ---#DES 'io' ---@class iolib @@ -11,18 +11,18 @@ io = {} ---@alias openmode ----|>'"r"' # ---#DESTAIL 'openmode.r' ----| '"w"' # ---#DESTAIL 'openmode.w' ----| '"a"' # ---#DESTAIL 'openmode.a' ----| '"r+"' # ---#DESTAIL 'openmode.r+' ----| '"w+"' # ---#DESTAIL 'openmode.w+' ----| '"a+"' # ---#DESTAIL 'openmode.a+' ----| '"rb"' # ---#DESTAIL 'openmode.rb' ----| '"wb"' # ---#DESTAIL 'openmode.wb' ----| '"ab"' # ---#DESTAIL 'openmode.ab' ----| '"r+b"' # ---#DESTAIL 'openmode.r+b' ----| '"w+b"' # ---#DESTAIL 'openmode.w+b' ----| '"a+b"' # ---#DESTAIL 'openmode.a+b' +---|>"r" # ---#DESTAIL 'openmode.r' +---| "w" # ---#DESTAIL 'openmode.w' +---| "a" # ---#DESTAIL 'openmode.a' +---| "r+" # ---#DESTAIL 'openmode.r+' +---| "w+" # ---#DESTAIL 'openmode.w+' +---| "a+" # ---#DESTAIL 'openmode.a+' +---| "rb" # ---#DESTAIL 'openmode.rb' +---| "wb" # ---#DESTAIL 'openmode.wb' +---| "ab" # ---#DESTAIL 'openmode.ab' +---| "r+b" # ---#DESTAIL 'openmode.r+b' +---| "w+b" # ---#DESTAIL 'openmode.w+b' +---| "a+b" # ---#DESTAIL 'openmode.a+b' ---#DES 'io.close' ---@param file? file* @@ -59,8 +59,8 @@ function io.open(filename, mode) end function io.output(file) end ---@alias popenmode ----| '"r"' # ---#DESTAIL 'popenmode.r' ----| '"w"' # ---#DESTAIL 'popenmode.w' +---| "r" # ---#DESTAIL 'popenmode.r' +---| "w" # ---#DESTAIL 'popenmode.w' ---#DES 'io.popen' ---@param prog string @@ -72,7 +72,7 @@ function io.popen(prog, mode) end ---#DES 'io.read' ---@param ... readmode ---@return any ----@return ... +---@return any ... ---@nodiscard function io.read(...) end @@ -82,9 +82,9 @@ function io.read(...) end function io.tmpfile() end ---@alias filetype ----| '"file"' # ---#DESTAIL 'filetype.file' ----| '"closed file"' # ---#DESTAIL 'filetype.closed file' ----| 'nil' # ---#DESTAIL 'filetype.nil' +---| "file" # ---#DESTAIL 'filetype.file' +---| "closed file" # ---#DESTAIL 'filetype.closed file' +---| `nil` # ---#DESTAIL 'filetype.nil' ---#DES 'io.type' ---@param file file* @@ -101,22 +101,22 @@ function io.write(...) end ---@class file* local file = {} ----@alias readmode integer +---@alias readmode integer|string ---#if VERSION >= 5.3 then ----| '"n"' # ---#DESTAIL 'readmode.n' ----| '"a"' # ---#DESTAIL 'readmode.a' ----|>'"l"' # ---#DESTAIL 'readmode.l' ----| '"L"' # ---#DESTAIL 'readmode.L' +---| "n" # ---#DESTAIL 'readmode.n' +---| "a" # ---#DESTAIL 'readmode.a' +---|>"l" # ---#DESTAIL 'readmode.l' +---| "L" # ---#DESTAIL 'readmode.L' ---#else ----| '"*n"' # ---#DESTAIL 'readmode.n' ----| '"*a"' # ---#DESTAIL 'readmode.a' ----|>'"*l"' # ---#DESTAIL 'readmode.l' +---| "*n" # ---#DESTAIL 'readmode.n' +---| "*a" # ---#DESTAIL 'readmode.a' +---|>"*l" # ---#DESTAIL 'readmode.l' ---#if JIT then ----| '"*L"' # ---#DESTAIL 'readmode.L' +---| "*L" # ---#DESTAIL 'readmode.L' ---#end ---#end ----@alias exitcode '"exit"'|'"signal"' +---@alias exitcode "exit"|"signal" ---#DES 'file:close' ---@return boolean? suc @@ -135,14 +135,14 @@ function file:lines(...) end ---#DES 'file:read' ---@param ... readmode ---@return any ----@return ... +---@return any ... ---@nodiscard function file:read(...) end ---@alias seekwhence ----| '"set"' # ---#DESTAIL 'seekwhence.set' ----|>'"cur"' # ---#DESTAIL 'seekwhence.cur' ----| '"end"' # ---#DESTAIL 'seekwhence.end' +---| "set" # ---#DESTAIL 'seekwhence.set' +---|>"cur" # ---#DESTAIL 'seekwhence.cur' +---| "end" # ---#DESTAIL 'seekwhence.end' ---#DES 'file:seek' ---@param whence? seekwhence @@ -152,9 +152,9 @@ function file:read(...) end function file:seek(whence, offset) end ---@alias vbuf ----| '"no"' # ---#DESTAIL 'vbuf.no' ----| '"full"' # ---#DESTAIL 'vbuf.full' ----| '"line"' # ---#DESTAIL 'vbuf.line' +---| "no" # ---#DESTAIL 'vbuf.no' +---| "full" # ---#DESTAIL 'vbuf.full' +---| "line" # ---#DESTAIL 'vbuf.line' ---#DES 'file:setvbuf' ---@param mode vbuf diff --git a/meta/template/jit.lua b/meta/template/jit.lua index 6397505e9..b047ba4e0 100644 --- a/meta/template/jit.lua +++ b/meta/template/jit.lua @@ -1,33 +1,43 @@ ---#if not JIT then DISABLE() end ----@meta +---@meta jit ---@version JIT ---@class jitlib ---@field version string ---@field version_num number ----@field os string ----@field arch string +---@field os 'Windows'|'Linux'|'OSX'|'BSD'|'POSIX'|'Other' +---@field arch 'x86'|'x64'|'arm'|'arm64'|'arm64be'|'ppc'|'ppc64'|'ppc64le'|'mips'|'mipsel'|'mips64'|'mips64el'|string jit = {} ----@overload fun() +---@overload fun(...):... ---@param func function|boolean ---@param recursive? boolean -function jit.on(func, recursive) end +function jit.on(func, recursive) +end ----@overload fun() +---@overload fun(...):... ---@param func function|boolean ---@param recursive? boolean -function jit.off(func, recursive) end +function jit.off(func, recursive) +end ----@overload fun() +---@overload fun(...):... ---@overload fun(tr: number) ---@param func function|boolean ---@param recursive? boolean -function jit.flush(func, recursive) end +function jit.flush(func, recursive) +end ---@return boolean status ----@return ... +---@return string ... ---@nodiscard -function jit.status() end +function jit.status() +end + +jit.opt = {} + +---@param ... any flags +function jit.opt.start(...) +end return jit diff --git a/meta/template/jit.profile.lua b/meta/template/jit.profile.lua new file mode 100644 index 000000000..a7db77bf0 --- /dev/null +++ b/meta/template/jit.profile.lua @@ -0,0 +1,20 @@ +---#if not JIT then DISABLE() end +---@meta jit.profile + +local profile = {} + +---@param mode string +---@param func fun(L: thread, samples: integer, vmst: string) +function profile.start(mode, func) +end + +function profile.stop() +end + +---@overload fun(th: thread, fmt: string, depth: integer) +---@param fmt string +---@param depth integer +function profile.dumpstack(fmt, depth) +end + +return profile \ No newline at end of file diff --git a/meta/template/jit.util.lua b/meta/template/jit.util.lua new file mode 100644 index 000000000..cf525527a --- /dev/null +++ b/meta/template/jit.util.lua @@ -0,0 +1,120 @@ +---#if not JIT then DISABLE() end +---@meta jit.util + +---@class Trace +---@class Proto + +local util = {} + +---@class jit.funcinfo.lua +local funcinfo = { + linedefined = 0, + lastlinedefined = 0, + stackslots = 0, + params = 0, + bytecodes = 0, + gcconsts = 0, + nconsts = 0, + upvalues = 0, + currentline = 0, + isvararg = false, + children = false, + source = "", + loc = "", + ---@type Proto[] + proto = {} +} + +---@class jit.funcinfo.c +---@field ffid integer|nil +local funcinfo2 = { + addr = 0, + upvalues = 0, +} + + +---@param func function +---@param pc? integer +---@return jit.funcinfo.c|jit.funcinfo.lua info +function util.funcinfo(func, pc) +end + +---@param func function +---@param pc integer +---@return integer? ins +---@return integer? m +function util.funcbc(func, pc) +end + +---@param func function +---@param idx integer +---@return any? k +function util.funck(func, idx) +end + +---@param func function +---@param idx integer +---@return string? name +function util.funcuvname(func, idx) +end + +---@class jit.traceinfo +local traceinfo = { + nins = 0, + nk = 0, + link = 0, + nexit = 0, + linktype = "" +} + +---@param tr Trace +---@return jit.traceinfo? info +function util.traceinfo(tr) +end + +---@param tr Trace +---@param ref integer +---@return integer? m +---@return integer? ot +---@return integer? op1 +---@return integer? op2 +---@return integer? prev +function util.traceir(tr, ref) +end + +---@param tr Trace +---@param idx integer +---@return any? k +---@return integer? t +---@return integer? slot +function util.tracek(tr, idx) +end + +---@class jit.snap : integer[] + +---@param tr Trace +---@param sn integer +---@return jit.snap? snap +function util.tracesnap(tr, sn) +end + +---@param tr Trace +---@return string? mcode +---@return integer? addr +---@return integer? loop +function util.tracemc(tr) +end + +---@overload fun(exitno: integer): integer +---@param tr Trace +---@param exitno integer +---@return integer? addr +function util.traceexitstub(tr, exitno) +end + +---@param idx integer +---@return integer? addr +function util.ircalladdr(idx) +end + +return util diff --git a/meta/template/math.lua b/meta/template/math.lua index 218a15aca..37e1d5c74 100644 --- a/meta/template/math.lua +++ b/meta/template/math.lua @@ -1,4 +1,4 @@ ----@meta +---@meta math ---#DES 'math' ---@class mathlib @@ -15,8 +15,9 @@ math = {} ---#DES 'math.abs' ----@param x number ----@return number +---@generic Number: number +---@param x Number +---@return Number ---@nodiscard function math.abs(x) end diff --git a/meta/template/os.lua b/meta/template/os.lua index 760c029a4..4dbffc6b2 100644 --- a/meta/template/os.lua +++ b/meta/template/os.lua @@ -1,4 +1,4 @@ ----@meta +---@meta os ---#DES 'os' ---@class oslib @@ -44,7 +44,7 @@ function os.date(format, time) end function os.difftime(t2, t1) end ---#DES 'os.execute' ----#if VERSION <= 5.1 then +---#if VERSION <= 5.1 and not JIT then ---@param command? string ---@return integer code function os.execute(command) end @@ -87,12 +87,12 @@ function os.remove(filename) end function os.rename(oldname, newname) end ---@alias localecategory ----|>'"all"' ----| '"collate"' ----| '"ctype"' ----| '"monetary"' ----| '"numeric"' ----| '"time"' +---|>"all" +---| "collate" +---| "ctype" +---| "monetary" +---| "numeric" +---| "time" ---#DES 'os.setlocale' ---@param locale string|nil @@ -100,8 +100,28 @@ function os.rename(oldname, newname) end ---@return string localecategory function os.setlocale(locale, category) end +---@class osdateparam +---#DES 'osdate.year' +---@field year integer|string +---#DES 'osdate.month' +---@field month integer|string +---#DES 'osdate.day' +---@field day integer|string +---#DES 'osdate.hour' +---@field hour (integer|string)? +---#DES 'osdate.min' +---@field min (integer|string)? +---#DES 'osdate.sec' +---@field sec (integer|string)? +---#DES 'osdate.wday' +---@field wday (integer|string)? +---#DES 'osdate.yday' +---@field yday (integer|string)? +---#DES 'osdate.isdst' +---@field isdst boolean? + ---#DES 'os.time' ----@param date? osdate +---@param date? osdateparam ---@return integer ---@nodiscard function os.time(date) end diff --git a/meta/template/package.lua b/meta/template/package.lua index 5c08bbeff..3845e7694 100644 --- a/meta/template/package.lua +++ b/meta/template/package.lua @@ -1,4 +1,4 @@ ----@meta +---@meta package ---#if VERSION >=5.4 then ---#DES 'require>5.4' diff --git a/meta/3rd/OpenResty/library/string.buffer.lua b/meta/template/string.buffer.lua similarity index 99% rename from meta/3rd/OpenResty/library/string.buffer.lua rename to meta/template/string.buffer.lua index 70ecc6d8e..7285290a4 100644 --- a/meta/3rd/OpenResty/library/string.buffer.lua +++ b/meta/template/string.buffer.lua @@ -1,6 +1,7 @@ ----@meta - +---#if not JIT then DISABLE() end +---@meta string.buffer +---@version JIT --- The string buffer library allows high-performance manipulation of string-like data. --- --- Unlike Lua strings, which are constants, string buffers are mutable sequences of 8-bit (binary-transparent) characters. Data can be stored, formatted and encoded into a string buffer and later converted, extracted or decoded. @@ -10,7 +11,7 @@ --- The string buffer libary also includes a high-performance serializer for Lua objects. --- --- ----## Streaming Serialization +--- ## Streaming Serialization --- --- In some contexts, it's desirable to do piecewise serialization of large datasets, also known as streaming. --- @@ -88,7 +89,7 @@ --- 0x1fe0.. โ†’ 0xff n.I --- ``` --- ----## Error handling +--- ## Error handling --- --- Many of the buffer methods can throw an error. Out-of-memory or usage errors are best caught with an outer wrapper for larger parts of code. There's not much one can do after that, anyway. --- @@ -101,7 +102,7 @@ --- end --- ``` --- ----## FFI caveats +--- ## FFI caveats --- --- The string buffer library has been designed to work well together with the FFI library. But due to the low-level nature of the FFI library, some care needs to be taken: --- @@ -126,6 +127,7 @@ local buffer = {} --- --- The maximum size of a single buffer is the same as the maximum size of a Lua string, which is slightly below two gigabytes. For huge data sizes, neither strings nor buffers are the right data structure โ€” use the FFI library to directly map memory or files up to the virtual memory limit of your OS. --- +---@version JIT ---@class string.buffer : table local buf = {} diff --git a/meta/template/string.lua b/meta/template/string.lua index 47f0f49ec..e23b0803f 100644 --- a/meta/template/string.lua +++ b/meta/template/string.lua @@ -1,15 +1,14 @@ ----@meta +---@meta string ---#DES 'string' ---@class stringlib string = {} ---#DES 'string.byte' ----@param s string +---@param s string|number ---@param i? integer ---@param j? integer ----@return integer ----@return ... +---@return integer ... ---@nodiscard function string.byte(s, i, j) end @@ -17,30 +16,36 @@ function string.byte(s, i, j) end ---@param byte integer ---@param ... integer ---@return string ----@return ... ---@nodiscard function string.char(byte, ...) end ---#DES 'string.dump' ----@param f async fun() +---#if VERSION >= 5.3 or JIT then +---@param f async fun(...):... ---@param strip? boolean ---@return string ---@nodiscard function string.dump(f, strip) end +---#else +---@param f async fun(...):... +---@return string +---@nodiscard +function string.dump(f) end +---#end ---#DES 'string.find' ----@param s string ----@param pattern string +---@param s string|number +---@param pattern string|number ---@param init? integer ---@param plain? boolean ----@return integer start ----@return integer end ----@return ... captured +---@return integer|nil start +---@return integer|nil end +---@return any|nil ... captured ---@nodiscard function string.find(s, pattern, init, plain) end ---#DES 'string.format' ----@param s any +---@param s string|number ---@param ... any ---@return string ---@nodiscard @@ -48,22 +53,22 @@ function string.format(s, ...) end ---#DES 'string.gmatch' ---#if VERSION <= 5.3 then ----@param s string ----@param pattern string +---@param s string|number +---@param pattern string|number ---@return fun():string, ... ---@nodiscard function string.gmatch(s, pattern) end ---#else ----@param s string ----@param pattern string +---@param s string|number +---@param pattern string|number ---@param init? integer ---@return fun():string, ... function string.gmatch(s, pattern, init) end ---#end ---#DES 'string.gsub' ----@param s string ----@param pattern string +---@param s string|number +---@param pattern string|number ---@param repl string|number|table|function ---@param n? integer ---@return string @@ -72,22 +77,22 @@ function string.gmatch(s, pattern, init) end function string.gsub(s, pattern, repl, n) end ---#DES 'string.len' ----@param s string +---@param s string|number ---@return integer ---@nodiscard function string.len(s) end ---#DES 'string.lower' ----@param s string +---@param s string|number ---@return string ---@nodiscard function string.lower(s) end ---#DES 'string.match' ----@param s string ----@param pattern string +---@param s string|number +---@param pattern string|number ---@param init? integer ----@return ... captured +---@return any ... ---@nodiscard function string.match(s, pattern, init) end @@ -95,6 +100,7 @@ function string.match(s, pattern, init) end ---#DES 'string.pack' ---@param fmt string ---@param v1 string|number +---@param v2? string|number ---@param ... string|number ---@return string binary ---@nodiscard @@ -109,29 +115,29 @@ function string.packsize(fmt) end ---#if VERSION <= 5.1 and not JIT then ---#DES 'string.rep<5.1' ----@param s string +---@param s string|number ---@param n integer ---@return string ---@nodiscard function string.rep(s, n) end ---#else ---#DES 'string.rep>5.2' ----@param s string +---@param s string|number ---@param n integer ----@param sep? string +---@param sep? string|number ---@return string ---@nodiscard function string.rep(s, n, sep) end ---#end ---#DES 'string.reverse' ----@param s string +---@param s string|number ---@return string ---@nodiscard function string.reverse(s) end ---#DES 'string.sub' ----@param s string +---@param s string|number ---@param i integer ---@param j? integer ---@return string @@ -143,13 +149,13 @@ function string.sub(s, i, j) end ---@param fmt string ---@param s string ---@param pos? integer ----@return ... +---@return any ... ---@return integer offset ---@nodiscard function string.unpack(fmt, s, pos) end ---#DES 'string.upper' ----@param s string +---@param s string|number ---@return string ---@nodiscard function string.upper(s) end diff --git a/meta/template/table.clear.lua b/meta/template/table.clear.lua index dffed5fab..122e3651c 100644 --- a/meta/template/table.clear.lua +++ b/meta/template/table.clear.lua @@ -1,9 +1,9 @@ ---#if not JIT then DISABLE() end ----@meta +---@meta table.clear ---@version JIT ---#DES 'table.clear' ---@param tab table local function clear(tab) end -return clear \ No newline at end of file +return clear diff --git a/meta/template/table.lua b/meta/template/table.lua index 5b018ea5c..b36438d16 100644 --- a/meta/template/table.lua +++ b/meta/template/table.lua @@ -1,4 +1,4 @@ ----@meta +---@meta table ---#DES 'table' ---@class tablelib @@ -27,7 +27,7 @@ function table.insert(list, pos, value) end ---@nodiscard function table.maxn(table) end ----@version >5.3 +---@version >5.3, JIT ---#DES 'table.move' ---@param a1 table ---@param f integer diff --git a/meta/template/table.new.lua b/meta/template/table.new.lua index 398bc9f99..f4b05cdc6 100644 --- a/meta/template/table.new.lua +++ b/meta/template/table.new.lua @@ -1,5 +1,5 @@ ---#if not JIT then DISABLE() end ----@meta +---@meta table.new ---@version JIT ---#DES 'table.new' @@ -8,4 +8,4 @@ ---@return table local function new(narray, nhash) end -return new \ No newline at end of file +return new diff --git a/meta/template/utf8.lua b/meta/template/utf8.lua index 1797a5e84..5c7668384 100644 --- a/meta/template/utf8.lua +++ b/meta/template/utf8.lua @@ -1,5 +1,5 @@ ---#if VERSION <= 5.2 then DISABLE() end ----@meta +---@meta utf8 ---@version >5.3 ---#DES 'utf8' @@ -70,7 +70,7 @@ function utf8.len(s, i, j, lax) end ---#DES 'utf8.offset' ---@param s string ---@param n integer ----@param i integer +---@param i? integer ---@return integer p ---@nodiscard function utf8.offset(s, n, i) end diff --git a/meta/whimsical/basic.lua b/meta/whimsical/basic.lua new file mode 100644 index 000000000..9075a0e95 --- /dev/null +++ b/meta/whimsical/basic.lua @@ -0,0 +1,50 @@ +---@meta _ + +---#DES 'arg' +---@type string[] +arg = {} + +---#DES 'assert' +---@generic T +---@param v? T +---@param message? any +---@param ... any +---@return T +---@return any ... => args[reti + 1] +---@throw => args[1].isFalsy +---@narrow v => args[1].truly +function assert(v, message, ...) end + +--[[@@@ +---@overload fun(opt: 'collect') # ---#DESTAIL 'cgopt.collect' +---@overload fun(opt: 'stop') # ---#DESTAIL 'cgopt.stop' +---@overload fun(opt: 'restart') # ---#DESTAIL 'cgopt.restart' +---@overload fun(opt: 'count'): integer # ---#DESTAIL 'cgopt.count' +---@overload fun(opt: 'step'): boolean # ---#DESTAIL 'cgopt.step' +---@overload fun(opt: 'isrunning'): boolean # ---#DESTAIL 'cgopt.isrunning' +---#if VERSION >= 5.4 then +---@overload fun(opt: 'incremental' + , pause?: integer + , stepmul?: integer + , stepsize?: integer) # ---#DESTAIL 'cgopt.incremental' +---@overload fun(opt: 'generational' + , minor?: integer + , major?: integer) # ---#DESTAIL 'cgopt.generational' +---#end +---@overload fun(opt: 'setpause', arg: integer) # ---#DESTAIL 'cgopt.setpause' +---@overload fun(opt: 'setstepmul', arg: integer) # ---#DESTAIL 'cgopt.setstepmul' +---@prototype +]] +function collectgarbage(...) end + +---#DES 'dofile' +---@param filename? string +---@return any +---@custom dofile +function dofile(filename) end + +---#DES 'error' +---@param message any +---@param level? integer +---@throw +function error(message, level) end diff --git a/meta/whimsical/builtin.lua b/meta/whimsical/builtin.lua new file mode 100644 index 000000000..179057989 --- /dev/null +++ b/meta/whimsical/builtin.lua @@ -0,0 +1,18 @@ +---@meta _ + +---@class nil +---@class never +---@class true +---@class false +---@class any: { [unknown]: any } +---@class truly: { [unknown]: any } +---@class unknown: truly | false +---@class boolean +---@class number +---@class thread +---@class table: { [unknown]: any } +---@class table: { [K]: V } +---@class string: stringlib +---@class userdata: { [unknown]: any } +---@class lightuserdata +---@class function: fun(...): ... diff --git a/meta/whimsical/rule.lua b/meta/whimsical/rule.lua new file mode 100644 index 000000000..0715d78f8 --- /dev/null +++ b/meta/whimsical/rule.lua @@ -0,0 +1,128 @@ +local cat + +cat.rule.default = function (self) + self.isTruly = true + self.truly = self + self.falsy = cat.class 'never' + self.view = self.name +end + +cat.rule.never = function (self) + self.isTruly = nil +end + +cat.rule.any = function (self) + self.isTruly = nil + self.truly = cat.class 'truly' + self.falsy = cat.boolean(false) | cat.class 'nil' +end + +cat.rule['nil'] = function (self) + self.isTruly = false + self.truly = cat.class 'never' + self.falsy = self +end + +cat.rule.boolean = function (self) + if self.value == true then + self.isTruly = true + self.truly = self + self.falsy = cat.class 'never' + elseif self.value == false then + self.isTruly = false + self.truly = cat.class 'never' + self.falsy = self + else + self.isTruly = nil + self.truly = cat.boolean(true) + self.falsy = cat.boolean(false) + end +end + +cat.rule.number = function (self) + self.isTruly = true + self.truly = self + self.falsy = cat.class 'never' + self.view = tostring(self.value) +end + +cat.rule.integer = function (self) + self.isTruly = true + self.truly = self + self.falsy = cat.class 'never' + self.view = tostring(self.value) +end + +cat.rule.string = function (self) + self.isTruly = true + self.truly = self + self.falsy = cat.class 'never' + self.view = cat.util.viewString(self.value, self.quotation) +end + +cat.rule.union = function (self) + self.isTruly = function (union) + local isTruly = union.subs[1].isTruly + if isTruly == nil then + return nil + end + if isTruly == true then + for i = 2, #union.subs do + if union.subs[i].isTruly ~= true then + return nil + end + end + return true + else + for i = 2, #union.subs do + if union.subs[i].isTruly ~= false then + return nil + end + end + return false + end + return nil + end + self.truly = function (union) + local new = cat.union() + for i = 1, #union.subs do + new:add(union.subs[i].truly) + end + if new:len() == 0 then + return cat.class 'never' + end + if new:len() == 1 then + return new[1] + end + return new + end + self.falsy = function (union) + local new = cat.union() + for i = 1, #union.subs do + new:add(union.subs[i].falsy) + end + if new:len() == 0 then + return cat.class 'never' + end + if new:len() == 1 then + return new[1] + end + return new + end + self.view = function (union) + local views = {} + for i = 1, #union.subs do + views[i] = union.subs[i].view + end + if #views == 0 then + return 'never' + end + return table.concat(views, '|') + end +end + +cat.custom.dofile.onReturn = function (context) + local filename = context.args[1].asString + local file = cat.files[filename] + return file.returns[1] +end diff --git a/script/await.lua b/script/await.lua index fa2aea139..227455700 100644 --- a/script/await.lua +++ b/script/await.lua @@ -108,6 +108,11 @@ function m.hasID(id, co) return m.idMap[id] and m.idMap[id][co] ~= nil end +function m.unique(id, callback) + m.close(id) + m.setID(id, callback) +end + --- ไผ‘็œ ไธ€ๆฎตๆ—ถ้—ด ---@param time number ---@async diff --git a/script/brave/brave.lua b/script/brave/brave.lua index 36b56b547..9ad7ebd1e 100644 --- a/script/brave/brave.lua +++ b/script/brave/brave.lua @@ -10,7 +10,7 @@ m.ability = {} m.queue = {} --- ๆณจๅ†Œๆˆไธบๅ‹‡่€… -function m.register(id) +function m.register(id, privatePad) m.id = id if #m.queue > 0 then @@ -20,7 +20,7 @@ function m.register(id) end m.queue = nil - m.start() + m.start(privatePad) end --- ๆณจๅ†Œ่ƒฝๅŠ› @@ -41,22 +41,24 @@ function m.push(name, params) end --- ๅผ€ๅง‹ๆ‰พๅทฅไฝœ -function m.start() +function m.start(privatePad) + local reqPad = privatePad and thread.channel('req:' .. privatePad) or taskPad + local resPad = privatePad and thread.channel('res:' .. privatePad) or waiter m.push('mem', collectgarbage 'count') while true do - local name, id, params = taskPad:bpop() + local name, id, params = reqPad:bpop() local ability = m.ability[name] -- TODO if not ability then - waiter:push(m.id, id) + resPad:push(m.id, id) log.error('Brave can not handle this work: ' .. name) goto CONTINUE end local ok, res = xpcall(ability, log.error, params) if ok then - waiter:push(m.id, id, res) + resPad:push(m.id, id, res) else - waiter:push(m.id, id) + resPad:push(m.id, id) end m.push('mem', collectgarbage 'count') ::CONTINUE:: diff --git a/script/brave/work.lua b/script/brave/work.lua index 49c90f831..a6a7a41e0 100644 --- a/script/brave/work.lua +++ b/script/brave/work.lua @@ -1,11 +1,6 @@ local brave = require 'brave.brave' -local parser = require 'parser' -local fs = require 'bee.filesystem' -local furi = require 'file-uri' -local util = require 'utility' -local thread = require 'bee.thread' -brave.on('loadProto', function () +brave.on('loadProtoByStdio', function () local jsonrpc = require 'jsonrpc' while true do local proto, err = jsonrpc.decode(io.read) @@ -18,7 +13,57 @@ brave.on('loadProto', function () end end) +brave.on('loadProtoBySocket', function (param) + local jsonrpc = require 'jsonrpc' + local net = require 'service.net' + local buf = '' + + ---@async + local parser = coroutine.create(function () + while true do + ---@async + local proto, err = jsonrpc.decode(function (len) + while true do + if #buf >= len then + local res = buf:sub(1, len) + buf = buf:sub(len + 1) + return res + end + coroutine.yield() + end + end) + --log.debug('loaded proto', proto.method) + if not proto then + brave.push('protoerror', err) + return + end + brave.push('proto', proto) + end + end) + + local lsclient = net.connect('tcp', '127.0.0.1', param.port) + local lsmaster = net.connect('unix', param.unixPath) + + assert(lsclient) + assert(lsmaster) + + function lsclient:on_data(data) + buf = buf .. data + coroutine.resume(parser) + end + + function lsmaster:on_data(data) + lsclient:write(data) + net.update() + end + + while true do + net.update(10) + end +end) + brave.on('timer', function (time) + local thread = require 'bee.thread' while true do thread.sleep(time) brave.push('wakeup') @@ -26,5 +71,47 @@ brave.on('timer', function (time) end) brave.on('loadFile', function (path) + local util = require 'utility' return util.loadFile(path) end) + +brave.on('removeCaches', function (path) + local fs = require 'bee.filesystem' + local fsu = require 'fs-utility' + for dir in fs.pairs(fs.path(path)) do + local lockFile = dir / '.lock' + local f = io.open(lockFile:string(), 'wb') + if f then + f:close() + fsu.fileRemove(dir) + end + end +end) + +---@class brave.param.compile +---@field uri uri +---@field text string +---@field mode string +---@field version string +---@field options brave.param.compile.options + +---@class brave.param.compile.options +---@field special table +---@field unicodeName boolean +---@field nonstandardSymbol table + +---@param param brave.param.compile +brave.on('compile', function (param) + local parser = require 'parser' + local clock = os.clock() + local state, err = parser.compile(param.text + , param.mode + , param.version + , param.options + ) + log.debug('Async compile', param.uri, 'takes:', os.clock() - clock) + return { + state = state, + err = err, + } +end) diff --git a/script/cli/check.lua b/script/cli/check.lua index 4df94c593..5ac9ea139 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -4,10 +4,15 @@ local ws = require 'workspace' local files = require 'files' local diag = require 'provider.diagnostic' local util = require 'utility' -local json = require 'json-beautify' +local jsonb = require 'json-beautify' local lang = require 'language' local define = require 'proto.define' local config = require 'config.config' +local fs = require 'bee.filesystem' +local provider = require 'provider' + +require 'plugin' +require 'vm' lang(LOCALE) @@ -16,11 +21,13 @@ if type(CHECK) ~= 'string' then return end -local rootUri = furi.encode(CHECK) +local rootPath = fs.absolute(fs.path(CHECK)):string() +local rootUri = furi.encode(rootPath) if not rootUri then - print(lang.script('CLI_CHECK_ERROR_URI', CHECK)) + print(lang.script('CLI_CHECK_ERROR_URI', rootPath)) return end +rootUri = rootUri:gsub("/$", "") if CHECKLEVEL then if not define.DiagnosticSeverity[CHECKLEVEL] then @@ -48,18 +55,23 @@ lclient():start(function (client) io.write(lang.script('CLI_CHECK_INITING')) + provider.updateConfig(rootUri) + ws.awaitReady(rootUri) local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable')) for name, serverity in pairs(define.DiagnosticDefaultSeverity) do serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or 'Warning' + if serverity:sub(-1) == '!' then + serverity = serverity:sub(1, -2) + end if define.DiagnosticSeverity[serverity] > checkLevel then disables[name] = true end end - config.set(nil, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) + config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) - local uris = files.getAllUris(rootUri) + local uris = files.getChildFiles(rootUri) local max = #uris for i, uri in ipairs(uris) do files.open(uri) @@ -73,6 +85,7 @@ lclient():start(function (client) .. ('0'):rep(#tostring(max) - #tostring(i)) .. tostring(i) .. '/' .. tostring(max) io.write(output) + io.flush() end end io.write('\x0D') @@ -89,8 +102,11 @@ end if count == 0 then print(lang.script('CLI_CHECK_SUCCESS')) else - local outpath = LOGPATH .. '/check.json' - util.saveFile(outpath, json.beautify(results)) + local outpath = CHECK_OUT_PATH + if outpath == nil then + outpath = LOGPATH .. '/check.json' + end + util.saveFile(outpath, jsonb.beautify(results)) print(lang.script('CLI_CHECK_RESULTS', count, outpath)) end diff --git a/script/cli/doc.lua b/script/cli/doc.lua new file mode 100644 index 000000000..c413d354c --- /dev/null +++ b/script/cli/doc.lua @@ -0,0 +1,464 @@ +local lclient = require 'lclient' +local furi = require 'file-uri' +local ws = require 'workspace' +local files = require 'files' +local util = require 'utility' +local jsonb = require 'json-beautify' +local lang = require 'language' +local define = require 'proto.define' +local config = require 'config.config' +local await = require 'await' +local vm = require 'vm' +local guide = require 'parser.guide' +local getDesc = require 'core.hover.description' +local getLabel = require 'core.hover.label' +local doc2md = require 'cli.doc2md' +local progress = require 'progress' +local fs = require 'bee.filesystem' + +local export = {} + +---@async +local function packObject(source, mark) + if type(source) ~= 'table' then + return source + end + if not mark then + mark = {} + end + if mark[source] then + return + end + mark[source] = true + local new = {} + if (#source > 0 and next(source, #source) == nil) + or source.type == 'funcargs' then + new = {} + for i = 1, #source do + new[i] = packObject(source[i], mark) + end + else + for k, v in pairs(source) do + if k == 'type' + or k == 'name' + or k == 'start' + or k == 'finish' + or k == 'types' then + new[k] = packObject(v, mark) + end + end + if source.type == 'function' then + new['args'] = packObject(source.args, mark) + local _, _, max = vm.countReturnsOfFunction(source) + if max > 0 then + new.returns = {} + for i = 1, max do + local rtn = vm.getReturnOfFunction(source, i) + new.returns[i] = packObject(rtn) + end + end + new['view'] = getLabel(source, source.parent.type == 'setmethod') + end + if source.type == 'local' + or source.type == 'self' then + new['name'] = source[1] + end + if source.type == 'function.return' then + new['desc'] = source.comment and getDesc(source.comment) + new['rawdesc'] = source.comment and getDesc(source.comment, true) + end + if source.type == 'doc.type.table' then + new['fields'] = packObject(source.fields, mark) + end + if source.type == 'doc.field.name' + or source.type == 'doc.type.arg.name' then + new['[1]'] = packObject(source[1], mark) + new['view'] = source[1] + end + if source.type == 'doc.type.function' then + new['args'] = packObject(source.args, mark) + if source.returns then + new['returns'] = packObject(source.returns, mark) + end + end + if source.bindDocs then + new['desc'] = getDesc(source) + new['rawdesc'] = getDesc(source, true) + end + new['view'] = new['view'] or vm.getInfer(source):view(ws.rootUri) + end + return new +end + +---@async +local function getExtends(source) + if source.type == 'doc.class' then + if not source.extends then + return nil + end + return packObject(source.extends) + end + if source.type == 'doc.alias' then + if not source.extends then + return nil + end + return packObject(source.extends) + end +end + +---@async +---@param global vm.global +---@param results table +local function collectTypes(global, results) + if guide.isBasicType(global.name) then + return + end + local result = { + name = global.name, + type = 'type', + desc = nil, + rawdesc = nil, + defines = {}, + fields = {}, + } + for _, set in ipairs(global:getSets(ws.rootUri)) do + local uri = guide.getUri(set) + if files.isLibrary(uri) then + goto CONTINUE + end + result.defines[#result.defines+1] = { + type = set.type, + file = guide.getUri(set), + start = set.start, + finish = set.finish, + extends = getExtends(set), + } + result.desc = result.desc or getDesc(set) + result.rawdesc = result.rawdesc or getDesc(set, true) + ::CONTINUE:: + end + if #result.defines == 0 then + return + end + table.sort(result.defines, function (a, b) + if a.file ~= b.file then + return a.file < b.file + end + return a.start < b.start + end) + results[#results+1] = result + ---@async + ---@diagnostic disable-next-line: not-yieldable + vm.getClassFields(ws.rootUri, global, vm.ANY, function (source) + if source.type == 'doc.field' then + ---@cast source parser.object + if files.isLibrary(guide.getUri(source)) then + return + end + local field = {} + result.fields[#result.fields+1] = field + if source.field.type == 'doc.field.name' then + field.name = source.field[1] + else + field.name = ('[%s]'):format(vm.getInfer(source.field):view(ws.rootUri)) + end + field.type = source.type + field.file = guide.getUri(source) + field.start = source.start + field.finish = source.finish + field.desc = getDesc(source) + field.rawdesc = getDesc(source, true) + field.extends = packObject(source.extends) + field.visible = vm.getVisibleType(source) + return + end + if source.type == 'setfield' + or source.type == 'setmethod' then + ---@cast source parser.object + if files.isLibrary(guide.getUri(source)) then + return + end + local field = {} + result.fields[#result.fields+1] = field + field.name = (source.field or source.method)[1] + field.type = source.type + field.file = guide.getUri(source) + field.start = source.start + field.finish = source.finish + field.desc = getDesc(source) + field.rawdesc = getDesc(source, true) + field.extends = packObject(source.value) + field.visible = vm.getVisibleType(source) + if vm.isAsync(source, true) then + field.async = true + end + local depr = vm.getDeprecated(source) + if (depr and not depr.versions) then + field.deprecated = true + end + return + end + if source.type == 'tableindex' then + ---@cast source parser.object + if source.index.type ~= 'string' then + return + end + if files.isLibrary(guide.getUri(source)) then + return + end + local field = {} + result.fields[#result.fields+1] = field + field.name = source.index[1] + field.type = source.type + field.file = guide.getUri(source) + field.start = source.start + field.finish = source.finish + field.desc = getDesc(source) + field.rawdesc = getDesc(source, true) + field.extends = packObject(source.value) + field.visible = vm.getVisibleType(source) + return + end + end) + table.sort(result.fields, function (a, b) + if a.name ~= b.name then + return a.name < b.name + end + if a.file ~= b.file then + return a.file < b.file + end + return a.start < b.start + end) +end + +---@async +---@param global vm.global +---@param results table +local function collectVars(global, results) + local result = { + name = global:getCodeName(), + type = 'variable', + desc = nil, + defines = {}, + } + for _, set in ipairs(global:getSets(ws.rootUri)) do + if set.type == 'setglobal' + or set.type == 'setfield' + or set.type == 'setmethod' + or set.type == 'setindex' then + result.defines[#result.defines+1] = { + type = set.type, + file = guide.getUri(set), + start = set.start, + finish = set.finish, + extends = packObject(set.value), + } + result.desc = result.desc or getDesc(set) + result.rawdesc = result.rawdesc or getDesc(set, true) + result.defines[#result.defines].extends['desc'] = getDesc(set) + result.defines[#result.defines].extends['rawdesc'] = getDesc(set, true) + if vm.isAsync(set, true) then + result.defines[#result.defines].extends['async'] = true + end + local depr = vm.getDeprecated(set) + if (depr and not depr.versions) then + result.defines[#result.defines].extends['deprecated'] = true + end + end + end + if #result.defines == 0 then + return + end + table.sort(result.defines, function (a, b) + if a.file ~= b.file then + return a.file < b.file + end + return a.start < b.start + end) + results[#results+1] = result +end + +---Add config settings to JSON output. +---@param results table +local function collectConfig(results) + local result = { + name = 'LuaLS', + type = 'luals.config', + DOC = fs.absolute(fs.path(DOC)):string(), + defines = {}, + fields = {} + } + results[#results+1] = result +end + +---@async +---@param callback fun(i, max) +function export.export(outputPath, callback) + local results = {} + local globals = vm.getAllGlobals() + + collectConfig(results) + local max = 0 + for _ in pairs(globals) do + max = max + 1 + end + local i = 0 + for _, global in pairs(globals) do + if global.cate == 'variable' then + collectVars(global, results) + elseif global.cate == 'type' then + collectTypes(global, results) + end + i = i + 1 + callback(i, max) + end + + table.sort(results, function (a, b) + return a.name < b.name + end) + + local docPath = outputPath .. '/doc.json' + jsonb.supportSparseArray = true + util.saveFile(docPath, jsonb.beautify(results)) + + local mdPath = doc2md.buildMD(outputPath) + return docPath, mdPath +end + +function export.getDocOutputPath() + local doc_output_path = '' + if type(DOC_OUT_PATH) == 'string' then + doc_output_path = fs.absolute(fs.path(DOC_OUT_PATH)):string() + elseif DOC_OUT_PATH == true then + doc_output_path = fs.current_path():string() + else + doc_output_path = LOGPATH + end + return doc_output_path +end + +---@async +---@param outputPath string +function export.makeDoc(outputPath) + ws.awaitReady(ws.rootUri) + + local expandAlias = config.get(ws.rootUri, 'Lua.hover.expandAlias') + config.set(ws.rootUri, 'Lua.hover.expandAlias', false) + local _ = function () + config.set(ws.rootUri, 'Lua.hover.expandAlias', expandAlias) + end + + await.sleep(0.1) + + local prog = progress.create(ws.rootUri, 'ๆญฃๅœจ็”Ÿๆˆๆ–‡ๆกฃ...', 0) + local docPath, mdPath = export.export(outputPath, function (i, max) + prog:setMessage(('%d/%d'):format(i, max)) + prog:setPercentage((i) / max * 100) + end) + + return docPath, mdPath +end + + +---Find file 'doc.json'. +---@return fs.path +local function findDocJson() + local doc_json_path + if type(DOC_UPDATE) == 'string' then + doc_json_path = fs.absolute(fs.path(DOC_UPDATE)) .. '/doc.json' + else + doc_json_path = fs.current_path() .. '/doc.json' + end + if fs.exists(doc_json_path) then + return doc_json_path + else + error(string.format('Error: File "%s" not found.', doc_json_path)) + end +end + +---@return string # path of 'doc.json' +---@return string # path to be documented +local function getPathDocUpdate() + local doc_json_path = findDocJson() + local ok, doc_path = pcall( + function () + local json = require('json') + local json_file = io.open(doc_json_path:string(), 'r'):read('*all') + local json_data = json.decode(json_file) + for _, section in ipairs(json_data) do + if section.type == 'luals.config' then + return section.DOC + end + end + end) + if ok then + local doc_json_dir = doc_json_path:string():gsub('/doc.json', '') + return doc_json_dir, doc_path + else + error(string.format('Error: Cannot update "%s".', doc_json_path .. '/doc.json')) + end +end + +function export.runCLI() + lang(LOCALE) + + if DOC_UPDATE then + DOC_OUT_PATH, DOC = getPathDocUpdate() + end + + if type(DOC) ~= 'string' then + print(lang.script('CLI_CHECK_ERROR_TYPE', type(DOC))) + return + end + + local rootUri = furi.encode(fs.absolute(fs.path(DOC)):string()) + if not rootUri then + print(lang.script('CLI_CHECK_ERROR_URI', DOC)) + return + end + + print('root uri = ' .. rootUri) + + util.enableCloseFunction() + + local lastClock = os.clock() + + ---@async + lclient():start(function (client) + client:registerFakers() + + client:initialize { + rootUri = rootUri, + } + + io.write(lang.script('CLI_DOC_INITING')) + + config.set(nil, 'Lua.diagnostics.enable', false) + config.set(nil, 'Lua.hover.expandAlias', false) + + ws.awaitReady(rootUri) + await.sleep(0.1) + + local docPath, mdPath = export.export(export.getDocOutputPath(), function (i, max) + if os.clock() - lastClock > 0.2 then + lastClock = os.clock() + local output = '\x0D' + .. ('>'):rep(math.ceil(i / max * 20)) + .. ('='):rep(20 - math.ceil(i / max * 20)) + .. ' ' + .. ('0'):rep(#tostring(max) - #tostring(i)) + .. tostring(i) .. '/' .. tostring(max) + io.write(output) + end + end) + + io.write('\x0D') + + print(lang.script('CLI_DOC_DONE' + , ('[%s](%s)'):format(files.normalize(docPath), furi.encode(docPath)) + , ('[%s](%s)'):format(files.normalize(mdPath), furi.encode(mdPath)) + )) + end) +end + +return export diff --git a/script/cli/doc2md.lua b/script/cli/doc2md.lua new file mode 100644 index 000000000..70c1b2a08 --- /dev/null +++ b/script/cli/doc2md.lua @@ -0,0 +1,53 @@ +-- This is an example of how to process the generated `doc.json` file. +-- You can use it to generate a markdown file or a html file. + +local jsonc = require 'jsonc' +local util = require 'utility' +local markdown = require 'provider.markdown' + +local export = {} + +function export.buildMD(outputPath) + local doc = jsonc.decode_jsonc(util.loadFile(outputPath .. '/doc.json')) + local md = markdown() + + assert(type(doc) == 'table') + + for _, class in ipairs(doc) do + md:add('md', '# ' .. class.name) + md:emptyLine() + md:add('md', class.desc) + md:emptyLine() + if class.defines then + for _, define in ipairs(class.defines) do + if define.extends then + md:add('lua', define.extends.view) + md:emptyLine() + end + end + end + if class.fields then + local mark = {} + for _, field in ipairs(class.fields) do + if not mark[field.name] then + mark[field.name] = true + md:add('md', '## ' .. field.name) + md:emptyLine() + md:add('lua', field.extends.view) + md:emptyLine() + md:add('md', field.desc) + md:emptyLine() + end + end + end + md:splitLine() + end + + local mdPath = outputPath .. '/doc.md' + + util.saveFile(mdPath, md:string()) + + return mdPath +end + +return export diff --git a/script/cli/init.lua b/script/cli/init.lua index c2fc7aa84..d37c50ae1 100644 --- a/script/cli/init.lua +++ b/script/cli/init.lua @@ -7,3 +7,18 @@ if _G['CHECK'] then require 'cli.check' os.exit(0, true) end + +if _G['DOC_UPDATE'] then + require 'cli.doc' .runCLI() + os.exit(0, true) +end + +if _G['DOC'] then + require 'cli.doc' .runCLI() + os.exit(0, true) +end + +if _G['VISUALIZE'] then + local ret = require 'cli.visualize' .runCLI() + os.exit(ret or 0, true) +end diff --git a/script/cli/visualize.lua b/script/cli/visualize.lua new file mode 100644 index 000000000..29269b826 --- /dev/null +++ b/script/cli/visualize.lua @@ -0,0 +1,103 @@ +local lang = require 'language' +local parser = require 'parser' +local guide = require 'parser.guide' + +local function nodeId(node) + return node.type .. ':' .. node.start .. ':' .. node.finish +end + +local function shorten(str) + if type(str) ~= 'string' then + return str + end + str = str:gsub('\n', '\\\\n') + if #str <= 20 then + return str + else + return str:sub(1, 17) .. '...' + end +end + +local function getTooltipLine(k, v) + if type(v) == 'table' then + if v.type then + v = '' + else + v = '' + end + end + v = tostring(v) + v = v:gsub('"', '\\"') + return k .. ': ' .. shorten(v) .. '\\n' +end + +local function getTooltip(node) + local str = '' + local skipNodes = {parent = true, start = true, finish = true, type = true} + str = str .. getTooltipLine('start', node.start) + str = str .. getTooltipLine('finish', node.finish) + for k, v in pairs(node) do + if type(k) ~= 'number' and not skipNodes[k] then + str = str .. getTooltipLine(k, v) + end + end + for i = 1, math.min(#node, 15) do + str = str .. getTooltipLine(i, node[i]) + end + if #node > 15 then + str = str .. getTooltipLine('15..' .. #node, '(...)') + end + return str +end + +local nodeEntry = '\t"%s" [\n\t\tlabel="%s\\l%s\\l"\n\t\ttooltip="%s"\n\t]' +local function getNodeLabel(node) + local keyName = guide.getKeyName(node) + if node.type == 'binary' or node.type == 'unary' then + keyName = node.op.type + elseif node.type == 'label' or node.type == 'goto' then + keyName = node[1] + end + return nodeEntry:format(nodeId(node), node.type, shorten(keyName) or '', getTooltip(node)) +end + +local function getVisualizeVisitor(writer) + local function visitNode(node, parent) + if node == nil then return end + writer:write(getNodeLabel(node)) + writer:write('\n') + if parent then + writer:write(('\t"%s" -> "%s"'):format(nodeId(parent), nodeId(node))) + writer:write('\n') + end + guide.eachChild(node, function(child) + visitNode(child, node) + end) + end + return visitNode +end + + +local export = {} + +function export.visualizeAst(code, writer) + local state = parser.compile(code, 'Lua', _G['LUA_VER'] or 'Lua 5.4') + writer:write('digraph AST {\n') + writer:write('\tnode [shape = rect]\n') + getVisualizeVisitor(writer)(state.ast) + writer:write('}\n') +end + +function export.runCLI() + lang(LOCALE) + local file = _G['VISUALIZE'] + local code, err = io.open(file) + if not code then + io.stderr:write('failed to open ' .. file .. ': ' .. err) + return 1 + end + code = code:read('a') + return export.visualizeAst(code, io.stdout) +end + +return export diff --git a/script/client.lua b/script/client.lua index 7432e60b4..e328dc52a 100644 --- a/script/client.lua +++ b/script/client.lua @@ -6,10 +6,11 @@ local proto = require 'proto' local define = require 'proto.define' local config = require 'config' local converter = require 'proto.converter' -local json = require 'json-beautify' local await = require 'await' local scope = require 'workspace.scope' local inspect = require 'inspect' +local jsone = require 'json-edit' +local jsonc = require 'jsonc' local m = {} m._eventList = {} @@ -58,7 +59,7 @@ function m.getAbility(name) end current = current[parent] if not current then - return nil + return current end if nextPos > #name then break @@ -108,6 +109,7 @@ function m.showMessage(type, ...) type = define.MessageType[type] or 3, message = message, }) + log.info('ShowMessage', type, message) end ---@param type message.type @@ -127,11 +129,13 @@ function m.requestMessage(type, message, titles, callback) } map[title] = i end + log.info('requestMessage', type, message) proto.request('window/showMessageRequest', { type = define.MessageType[type] or 3, message = message, actions = actions, }, function (item) + log.info('responseMessage', message, item and item.title or nil) if item then callback(item.title, map[item.title]) else @@ -203,47 +207,200 @@ end ---@field global? boolean ---@field uri? uri ----@param cfg table ----@param uri uri +---@param uri uri? ---@param changes config.change[] ----@return boolean -local function applyConfig(cfg, uri, changes) +---@return config.change[] +local function getValidChanges(uri, changes) + local newChanges = {} + if not uri then + return changes + end local scp = scope.getScope(uri) - local ok = false for _, change in ipairs(changes) do if scp:isChildUri(change.uri) or scp:isLinkedUri(change.uri) then - 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 + newChanges[#newChanges+1] = change + end + end + return newChanges +end + +---@class json.patch +---@field op 'add' | 'remove' | 'replace' +---@field path string +---@field value any + +---@class json.patchInfo +---@field key string +---@field value any + +---@param cfg table +---@param rawKey string +---@return json.patchInfo +local function searchPatchInfo(cfg, rawKey) + + ---@param key string + ---@param parentKey string + ---@param parentValue table + ---@return json.patchInfo? + local function searchOnce(key, parentKey, parentValue) + if parentValue == nil then + return nil + end + if type(parentValue) ~= 'table' then + return { + key = parentKey, + value = parentValue, + } + end + if parentValue[key] then + return { + key = parentKey .. '/' .. key, + value = parentValue[key], + } + end + for pos in key:gmatch '()%.' do + local k = key:sub(1, pos - 1) + local v = parentValue[k] + local info = searchOnce(key:sub(pos + 1), parentKey .. '/' .. k, v) + if info then + return info end - ok = true + end + return nil + end + + return searchOnce(rawKey, '', cfg) + or searchOnce(rawKey:gsub('^Lua%.', ''), '', cfg) + or { + key = '/' .. rawKey:gsub('^Lua%.', ''), + value = nil, + } +end + +---@param uri? uri +---@param cfg table +---@param change config.change +---@return json.patch? +local function makeConfigPatch(uri, cfg, change) + local info = searchPatchInfo(cfg, change.key) + if change.action == 'add' then + if type(info.value) == 'table' and #info.value > 0 then + return { + op = 'add', + path = info.key .. '/-', + value = change.value, + } + else + return makeConfigPatch(uri, cfg, { + action = 'set', + key = change.key, + value = config.get(uri, change.key), + }) + end + elseif change.action == 'set' then + if info.value ~= nil then + return { + op = 'replace', + path = info.key, + value = change.value, + } + else + return { + op = 'add', + path = info.key, + value = change.value, + } + end + elseif change.action == 'prop' then + if type(info.value) == 'table' and next(info.value) then + return { + op = 'add', + path = info.key .. '/' .. change.prop, + value = change.value, + } + else + return makeConfigPatch(uri, cfg, { + action = 'set', + key = change.key, + value = config.get(uri, change.key), + }) + end + end + return nil +end + +---@param uri? uri +---@param path string +---@param changes config.change[] +---@return string? +local function editConfigJson(uri, path, changes) + local text = util.loadFile(path) + if not text then + m.showMessage('Error', lang.script('CONFIG_LOAD_FAILED', path)) + return nil + end + local suc, res = pcall(jsonc.decode_jsonc, text) + if not suc then + m.showMessage('Error', lang.script('CONFIG_MODIFY_FAIL_SYNTAX_ERROR', path .. res:match 'ERROR(.+)$')) + return nil + end + if type(res) ~= 'table' then + res = {} + end + ---@cast res table + for _, change in ipairs(changes) do + local patch = makeConfigPatch(uri, res, change) + if patch then + text = jsone.edit(text, patch, { indent = ' ' }) + end + end + return text +end + +---@param changes config.change[] +---@param applied config.change[] +local function removeAppliedChanges(changes, applied) + local appliedMap = {} + for _, change in ipairs(applied) do + appliedMap[change] = true + end + for i = #changes, 1, -1 do + if appliedMap[changes[i]] then + table.remove(changes, i) end end - return ok end local function tryModifySpecifiedConfig(uri, finalChanges) if #finalChanges == 0 then return false end + log.info('tryModifySpecifiedConfig', uri, inspect(finalChanges)) local workspace = require 'workspace' local scp = scope.getScope(uri) if scp:get('lastLocalType') ~= 'json' then + log.info('lastLocalType ~= json') return false end - local suc = applyConfig(scp:get('lastLocalConfig'), uri, finalChanges) - if not suc then + local validChanges = getValidChanges(uri, finalChanges) + if #validChanges == 0 then + log.info('No valid changes') return false end local path = workspace.getAbsolutePath(uri, CONFIGPATH) if not path then + log.info('Can not get absolute path') return false end - util.saveFile(path, json.beautify(scp:get('lastLocalConfig'), { indent = ' ' })) + local newJson = editConfigJson(uri, path, validChanges) + if not newJson then + log.info('Can not edit config json') + return false + end + util.saveFile(path, newJson) + log.info('Apply changes to config file', inspect(validChanges)) + removeAppliedChanges(finalChanges, validChanges) return true end @@ -251,30 +408,39 @@ local function tryModifyRC(uri, finalChanges, create) if #finalChanges == 0 then return false end + log.info('tryModifyRC', uri, inspect(finalChanges)) local workspace = require 'workspace' local path = workspace.getAbsolutePath(uri, '.luarc.jsonc') if not path then + log.info('Can not get absolute path of .luarc.jsonc') return false end path = fs.exists(fs.path(path)) and path or workspace.getAbsolutePath(uri, '.luarc.json') if not path then + log.info('Can not get absolute path of .luarc.json') return false end local buf = util.loadFile(path) if not buf and not create then + log.info('Can not load .luarc.json and not create') return false end - local loader = require 'config.loader' - local rc = loader.loadRCConfig(uri, path) or { - ['$schema'] = lang.id == 'zh-cn' - and [[https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema-zh-cn.json]] - or [[https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json]] - } - local suc = applyConfig(rc, uri, finalChanges) - if not suc then + local validChanges = getValidChanges(uri, finalChanges) + if #validChanges == 0 then + log.info('No valid changes') + return false + end + if not buf then + util.saveFile(path, '') + end + local newJson = editConfigJson(uri, path, validChanges) + if not newJson then + log.info('Can not edit config json') return false end - util.saveFile(path, json.beautify(rc, { indent = ' ' })) + util.saveFile(path, newJson) + log.info('Apply changes to .luarc.json', inspect(validChanges)) + removeAppliedChanges(finalChanges, validChanges) return true end @@ -282,6 +448,7 @@ local function tryModifyClient(uri, finalChanges) if #finalChanges == 0 then return false end + log.info('tryModifyClient', uri, inspect(finalChanges)) if not m.getOption 'changeConfiguration' then return false end @@ -294,12 +461,15 @@ local function tryModifyClient(uri, finalChanges) end end if #scpChanges == 0 then + log.info('No changes in client scope') return false end proto.notify('$/command', { command = 'lua.config', data = scpChanges, }) + log.info('Apply client changes', uri, inspect(scpChanges)) + removeAppliedChanges(finalChanges, scpChanges) return true end @@ -308,22 +478,43 @@ local function tryModifyClientGlobal(finalChanges) if #finalChanges == 0 then return end + log.info('tryModifyClientGlobal', inspect(finalChanges)) if not m.getOption 'changeConfiguration' then + log.info('Client dose not support modifying config') return end local changes = {} - for i = #finalChanges, 1, -1 do - local change = finalChanges[i] + for _, change in ipairs(finalChanges) do if change.global then changes[#changes+1] = change - finalChanges[i] = finalChanges[#finalChanges] - finalChanges[#finalChanges] = nil end end + if #changes == 0 then + log.info('No global changes') + return + end proto.notify('$/command', { command = 'lua.config', data = changes, }) + log.info('Apply client global changes', inspect(changes)) + removeAppliedChanges(finalChanges, changes) +end + +---@param changes config.change[] +---@return string +local function buildMaunuallyMessage(changes) + local message = {} + for _, change in ipairs(changes) do + if change.action == 'add' then + message[#message+1] = '* ' .. lang.script('WINDOW_MANUAL_CONFIG_ADD', change.key, change.value) + elseif change.action == 'set' then + message[#message+1] = '* ' .. lang.script('WINDOW_MANUAL_CONFIG_SET', change.key, change.value) + elseif change.action == 'prop' then + message[#message+1] = '* ' .. lang.script('WINDOW_MANUAL_CONFIG_PROP', change.key, change.prop, change.value) + end + end + return table.concat(message, '\n') end ---@param changes config.change[] @@ -354,25 +545,28 @@ function m.setConfig(changes, onlyMemory) if #finalChanges == 0 then return end + log.info('Modify config', inspect(finalChanges)) xpcall(function () local ws = require 'workspace' + tryModifyClientGlobal(finalChanges) if #ws.folders == 0 then + tryModifySpecifiedConfig(nil, finalChanges) tryModifyClient(nil, finalChanges) - return - end - tryModifyClientGlobal(finalChanges) - for _, scp in ipairs(ws.folders) do - if tryModifySpecifiedConfig(scp.uri, finalChanges) then - goto CONTINUE + if #finalChanges > 0 then + local manuallyModifyConfig = buildMaunuallyMessage(finalChanges) + m.showMessage('Warning', lang.script('CONFIG_MODIFY_FAIL_NO_WORKSPACE', manuallyModifyConfig)) end - if tryModifyRC(scp.uri, finalChanges, false) then - goto CONTINUE + else + for _, scp in ipairs(ws.folders) do + tryModifySpecifiedConfig(scp.uri, finalChanges) + tryModifyRC(scp.uri, finalChanges, false) + tryModifyClient(scp.uri, finalChanges) + tryModifyRC(scp.uri, finalChanges, true) end - if tryModifyClient(scp.uri, finalChanges) then - goto CONTINUE + if #finalChanges > 0 then + m.showMessage('Warning', lang.script('CONFIG_MODIFY_FAIL', buildMaunuallyMessage(finalChanges))) + log.warn('Config modify fail', inspect(finalChanges)) end - tryModifyRC(scp.uri, finalChanges, true) - ::CONTINUE:: end end, log.error) end @@ -382,21 +576,53 @@ end ---@param uri uri ---@param edits textEditor[] function m.editText(uri, edits) - local files = require 'files' + local files = require 'files' + local state = files.getState(uri) + if not state then + return + end local textEdits = {} for i, edit in ipairs(edits) do - textEdits[i] = converter.textEdit(converter.packRange(uri, edit.start, edit.finish), edit.text) + textEdits[i] = converter.textEdit(converter.packRange(state, edit.start, edit.finish), edit.text) end - proto.request('workspace/applyEdit', { + local params = { edit = { changes = { [uri] = textEdits, } } - }) + } + proto.request('workspace/applyEdit', params) + log.info('workspace/applyEdit', inspect(params)) +end + +---@alias textMultiEditor {uri: uri, start: integer, finish: integer, text: string} + +---@param editors textMultiEditor[] +function m.editMultiText(editors) + local files = require 'files' + local changes = {} + for _, editor in ipairs(editors) do + local uri = editor.uri + local state = files.getState(uri) + if state then + if not changes[uri] then + changes[uri] = {} + end + local edit = converter.textEdit(converter.packRange(state, editor.start, editor.finish), editor.text) + table.insert(changes[uri], edit) + end + end + local params = { + edit = { + changes = changes, + } + } + proto.request('workspace/applyEdit', params) + log.info('workspace/applyEdit', inspect(params)) end ----@param callback async fun() +---@param callback async fun(ev: string) function m.event(callback) m._eventList[#m._eventList+1] = callback end @@ -428,7 +654,7 @@ local function hookPrint() end function m.init(t) - log.debug('Client init', inspect(t)) + log.info('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 70e83fc8c..b10b0a6dd 100644 --- a/script/config/config.lua +++ b/script/config/config.lua @@ -76,7 +76,7 @@ end function m.set(uri, key, value) local unit = template[key] assert(unit, 'unknown key: ' .. key) - local scp = getScope(uri) + local scp = getScope(uri, key) local oldValue = m.get(uri, key) m.setByScope(scp, key, value) local newValue = m.get(uri, key) diff --git a/script/config/loader.lua b/script/config/loader.lua index 5cc7139fb..391424d53 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -30,7 +30,7 @@ function m.loadRCConfig(uri, filename) scp:set('lastRCConfig', nil) return nil end - local suc, res = pcall(jsonc.decode, buf) + local suc, res = pcall(jsonc.decode_jsonc, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return scp:get('lastRCConfig') @@ -54,14 +54,14 @@ function m.loadLocalConfig(uri, filename) end local buf = util.loadFile(path) if not buf then - errorMessage(lang.script('CONFIG_LOAD_FAILED', path)) + --errorMessage(lang.script('CONFIG_LOAD_FAILED', path)) scp:set('lastLocalConfig', nil) scp:set('lastLocalType', nil) return nil end local firstChar = buf:match '%S' if firstChar == '{' then - local suc, res = pcall(jsonc.decode, buf) + local suc, res = pcall(jsonc.decode_jsonc, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return scp:get('lastLocalConfig') diff --git a/script/config/template.lua b/script/config/template.lua index 17a33ab5e..4b6879079 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -181,6 +181,7 @@ end, function (self, ...) self.subs = { ... } end) +---@format disable-next local template = { ['Lua.runtime.version'] = Type.String >> 'Lua 5.4' << { 'Lua 5.1', @@ -209,6 +210,7 @@ local template = { 'assert', 'error', 'type', + 'os.exit', } ), ['Lua.runtime.meta'] = Type.String >> '${version} ${language} ${encoding}', @@ -221,8 +223,8 @@ local template = { '||', '&&', '!', '!=', 'continue', }), - ['Lua.runtime.plugin'] = Type.String, - ['Lua.runtime.pluginArgs'] = Type.Array(Type.String), + ['Lua.runtime.plugin'] = Type.Or(Type.String, Type.Array(Type.String)) , + ['Lua.runtime.pluginArgs'] = Type.Or(Type.Array(Type.String), Type.Hash(Type.String, Type.String)), ['Lua.runtime.fileEncoding'] = Type.String >> 'utf8' << { 'utf8', 'ansi', @@ -290,6 +292,11 @@ local template = { ) >> util.deepCopy(define.DiagnosticDefaultGroupFileStatus), ['Lua.diagnostics.disableScheme'] = Type.Array(Type.String) >> { 'git' }, + ['Lua.diagnostics.workspaceEvent'] = Type.String >> 'OnSave' << { + 'OnChange', + 'OnSave', + 'None', + }, ['Lua.diagnostics.workspaceDelay'] = Type.Integer >> 3000, ['Lua.diagnostics.workspaceRate'] = Type.Integer >> 100, ['Lua.diagnostics.libraryFiles'] = Type.String >> 'Opened' << { @@ -302,7 +309,7 @@ local template = { 'Opened', 'Disable', }, - ['Lua.diagnostics.unusedLocalExclude'] = Type.Array(Type.String), + ['Lua.diagnostics.unusedLocalExclude'] = Type.Array(Type.String), ['Lua.workspace.ignoreDir'] = Type.Array(Type.String) >> { '.vscode', }, @@ -311,9 +318,13 @@ local template = { ['Lua.workspace.maxPreload'] = Type.Integer >> 5000, ['Lua.workspace.preloadFileSize'] = Type.Integer >> 500, ['Lua.workspace.library'] = Type.Array(Type.String), - ['Lua.workspace.checkThirdParty'] = Type.Boolean >> true, + ['Lua.workspace.checkThirdParty'] = Type.Or(Type.String >> 'Ask' << { + 'Ask', + 'Apply', + 'ApplyInMemory', + 'Disable', + }, Type.Boolean), ['Lua.workspace.userThirdParty'] = Type.Array(Type.String), - ['Lua.workspace.supportScheme'] = Type.Array(Type.String) >> { 'file', 'untitled', 'git' }, ['Lua.completion.enable'] = Type.Boolean >> true, ['Lua.completion.callSnippet'] = Type.String >> 'Disable' << { 'Disable', @@ -369,18 +380,41 @@ local template = { }, ['Lua.window.statusBar'] = Type.Boolean >> true, ['Lua.window.progressBar'] = Type.Boolean >> true, + ['Lua.codeLens.enable'] = Type.Boolean >> false, ['Lua.format.enable'] = Type.Boolean >> true, ['Lua.format.defaultConfig'] = Type.Hash(Type.String, Type.String) >> {}, + ['Lua.typeFormat.config'] = Type.Hash(Type.String, Type.String) + >> { + format_line = "true", + auto_complete_end = "true", + auto_complete_table_sep = "true" + }, ['Lua.spell.dict'] = Type.Array(Type.String), + ['Lua.nameStyle.config'] = Type.Hash(Type.String, Type.Or(Type.String, Type.Array(Type.Hash(Type.String, Type.String)))) + >> {}, ['Lua.misc.parameters'] = Type.Array(Type.String), + ['Lua.misc.executablePath'] = Type.String, ['Lua.type.castNumberToInteger'] = Type.Boolean >> true, ['Lua.type.weakUnionCheck'] = Type.Boolean >> false, ['Lua.type.weakNilCheck'] = Type.Boolean >> false, + ['Lua.type.inferParamType'] = Type.Boolean >> false, + ['Lua.doc.privateName'] = Type.Array(Type.String), + ['Lua.doc.protectedName'] = Type.Array(Type.String), + ['Lua.doc.packageName'] = Type.Array(Type.String), -- VSCode + ["Lua.addonManager.enable"] = Type.Boolean >> true, ['files.associations'] = Type.Hash(Type.String, Type.String), - ['files.exclude'] = Type.Hash(Type.String, Type.Boolean), + -- copy from VSCode default + ['files.exclude'] = Type.Hash(Type.String, Type.Boolean) >> { + ["**/.DS_Store"] = true, + ["**/.git"] = true, + ["**/.hg"] = true, + ["**/.svn"] = true, + ["**/CVS"] = true, + ["**/Thumbs.db"] = true, + }, ['editor.semanticHighlighting.enabled'] = Type.Or(Type.Boolean, Type.String), ['editor.acceptSuggestionOnEnter'] = Type.String >> 'on', } diff --git a/script/core/code-action.lua b/script/core/code-action.lua index 4eb21ff82..720cd4c49 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -4,6 +4,11 @@ local util = require 'utility' local sp = require 'bee.subprocess' local guide = require "parser.guide" local converter = require 'proto.converter' +local autoreq = require 'core.completion.auto-require' +local rpath = require 'workspace.require-path' +local furi = require 'file-uri' +local undefined = require 'core.diagnostics.undefined-global' +local vm = require 'vm' ---@param uri uri ---@param row integer @@ -135,7 +140,7 @@ local function solveUndefinedGlobal(uri, diag, results) if not state then return end - local start = converter.unpackRange(uri, diag.range) + local start = converter.unpackRange(state, diag.range) guide.eachSourceContain(state.ast, start, function (source) if source.type ~= 'getglobal' then return @@ -157,7 +162,7 @@ local function solveLowercaseGlobal(uri, diag, results) if not state then return end - local start = converter.unpackRange(uri, diag.range) + local start = converter.unpackRange(state, diag.range) guide.eachSourceContain(state.ast, start, function (source) if source.type ~= 'setglobal' then return @@ -175,7 +180,7 @@ local function findSyntax(uri, diag) end for _, err in ipairs(state.errs) do if err.type:lower():gsub('_', '-') == diag.code then - local range = converter.packRange(uri, err.start, err.finish) + local range = converter.packRange(state, err.start, err.finish) if util.equal(range, diag.range) then return err end @@ -276,7 +281,11 @@ local function solveSyntax(uri, diag, results) end local function solveNewlineCall(uri, diag, results) - local start = converter.unpackRange(uri, diag.range) + local state = files.getState(uri) + if not state then + return + end + local start = converter.unpackRange(state, diag.range) results[#results+1] = { title = lang.script.ACTION_ADD_SEMICOLON, kind = 'quickfix', @@ -333,7 +342,7 @@ local function solveAwaitInSync(uri, diag, results) if not state then return end - local start, finish = converter.unpackRange(uri, diag.range) + local start, finish = converter.unpackRange(state, diag.range) local parentFunction guide.eachSourceType(state.ast, 'function', function (source) if source.start > finish @@ -369,6 +378,10 @@ local function solveAwaitInSync(uri, diag, results) end local function solveSpell(uri, diag, results) + local state = files.getState(uri) + if not state then + return + end local spell = require 'provider.spell' local word = diag.data if word == nil then @@ -401,8 +414,8 @@ local function solveSpell(uri, diag, results) changes = { [uri] = { { - start = converter.unpackPosition(uri, diag.range.start), - finish = converter.unpackPosition(uri, diag.range["end"]), + start = converter.unpackPosition(state, diag.range.start), + finish = converter.unpackPosition(state, diag.range["end"]), newText = suggest } } @@ -479,7 +492,7 @@ local function checkSwapParams(results, uri, start, finish) ) elseif source.type == 'funcargs' then local var = source.parent.parent - if guide.isSet(var) then + if guide.isAssign(var) then if var.type == 'tablefield' then var = var.field end @@ -624,24 +637,32 @@ local function checkJsonToLua(results, uri, start, finish) end local startOffset = guide.positionToOffset(state, start) local finishOffset = guide.positionToOffset(state, finish) - local jsonStart = text:match('()[%{%[]', startOffset + 1) + local jsonStart = text:match('()["%{%[]', startOffset + 1) if not jsonStart then return end - local jsonFinish + local jsonFinish, finishChar for i = math.min(finishOffset, #text), jsonStart + 1, -1 do local char = text:sub(i, i) if char == ']' or char == '}' then jsonFinish = i + finishChar = char break end end if not jsonFinish then return end - if not text:sub(jsonStart, jsonFinish):find '"%s*%:' then - return + if finishChar == '}' then + if not text:sub(jsonStart, jsonFinish):find '"%s*%:' then + return + end + end + if finishChar == ']' then + if not text:sub(jsonStart, jsonFinish):find ',' then + return + end end results[#results+1] = { title = lang.script.ACTION_JSON_TO_LUA, @@ -660,6 +681,54 @@ local function checkJsonToLua(results, uri, start, finish) } end +local function findRequireTargets(visiblePaths) + local targets = {} + for _, visible in ipairs(visiblePaths) do + targets[#targets+1] = visible.name + end + return targets +end + +local function checkMissingRequire(results, uri, start, finish) + local state = files.getState(uri) + local text = files.getText(uri) + if not state or not text then + return + end + + local function addRequires(global, endpos) + autoreq.check(state, global, endpos, function(moduleFile, stemname, targetSource) + local visiblePaths = rpath.getVisiblePath(uri, furi.decode(moduleFile)) + if not visiblePaths or #visiblePaths == 0 then return end + + for _, target in ipairs(findRequireTargets(visiblePaths)) do + results[#results+1] = { + title = lang.script('ACTION_AUTOREQUIRE', target, global), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = guide.getUri(state.ast), + target = moduleFile, + name = global, + requireName = target + }, + }, + } + } + end + end) + end + + guide.eachSourceBetween(state.ast, start, finish, function (source) + if vm.isUndefinedGlobal(source) then + addRequires(source[1], source.finish) + end + end) +end + return function (uri, start, finish, diagnostics) local ast = files.getState(uri) if not ast then @@ -672,6 +741,7 @@ return function (uri, start, finish, diagnostics) checkSwapParams(results, uri, start, finish) --checkExtractAsFunction(results, uri, start, finish) checkJsonToLua(results, uri, start, finish) + checkMissingRequire(results, uri, start, finish) return results end diff --git a/script/core/code-lens.lua b/script/core/code-lens.lua new file mode 100644 index 000000000..bc39ec865 --- /dev/null +++ b/script/core/code-lens.lua @@ -0,0 +1,142 @@ +local files = require 'files' +local guide = require 'parser.guide' +local await = require 'await' +local conv = require 'proto.converter' +local getRef = require 'core.reference' +local lang = require 'language' + +---@class parser.state +---@field package _codeLens? codeLens + +---@class codeLens.resolving +---@field mode 'reference' +---@field source? parser.object + +---@class codeLens.result +---@field position integer +---@field id integer + +---@class codeLens +local mt = {} +mt.__index = mt +mt.type = 'codeLens' +mt.id = 0 + +---@param uri uri +---@return boolean +function mt:init(uri) + self.state = files.getState(uri) + if not self.state then + return false + end + ---@type uri + self.uri = uri + ---@type codeLens.result[] + self.results = {} + ---@type table + self.resolving = {} + return true +end + +---@param pos integer +---@param resolving codeLens.resolving +function mt:addResult(pos, resolving) + self.id = self.id + 1 + self.results[#self.results+1] = { + position = pos, + id = self.id, + } + self.resolving[self.id] = resolving +end + +---@async +---@param id integer +---@return proto.command? +function mt:resolve(id) + local resolving = self.resolving[id] + if not resolving then + return nil + end + if resolving.mode == 'reference' then + return self:resolveReference(resolving.source) + end +end + +---@async +function mt:collectReferences() + await.delay() + ---@async + guide.eachSourceType(self.state.ast, 'function', function (src) + local parent = src.parent + if guide.isAssign(parent) then + src = parent + elseif parent.type == 'return' then + else + return + end + await.delay() + self:addResult(src.start, { + mode = 'reference', + source = src, + }) + end) +end + +---@async +---@param source parser.object +---@return proto.command? +function mt:resolveReference(source) + local refs = getRef(self.uri, source.finish, false) + local count = refs and #refs or 0 + local command = conv.command( + lang.script('COMMAND_REFERENCE_COUNT', count), + '', + {} + ) + return command +end + +---@async +---@param uri uri +---@return codeLens.result[]? +local function getCodeLens(uri) + local state = files.getState(uri) + if not state then + return nil + end + local codeLens = setmetatable({}, mt) + local suc = codeLens:init(uri) + if not suc then + return nil + end + state._codeLens = codeLens + + codeLens:collectReferences() + + if #codeLens.results == 0 then + return nil + end + + return codeLens.results +end + +---@async +---@param id integer +---@return proto.command? +local function resolve(uri, id) + local state = files.getState(uri) + if not state then + return nil + end + local codeLens = state._codeLens + if not codeLens then + return nil + end + local command = codeLens:resolve(id) + return command +end + +return { + codeLens = getCodeLens, + resolve = resolve, +} diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index 32911d923..9f3ff9297 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -132,9 +132,11 @@ end ---@async return function (data) + ---@type uri local uri = data.uri local target = data.target local name = data.name + local requireName = data.requireName local state = files.getState(uri) if not state then return @@ -149,11 +151,15 @@ return function (data) return #a.name < #b.name end) - local result = askAutoRequire(uri, visiblePaths) - if not result then - return + if not requireName then + requireName = askAutoRequire(uri, visiblePaths) + if not requireName then + return + end end local offset, fmt = findInsertRow(uri) - applyAutoRequire(uri, offset, name, result, fmt) + if offset and fmt then + applyAutoRequire(uri, offset, name, requireName, fmt) + end end diff --git a/script/core/command/exportDocument.lua b/script/core/command/exportDocument.lua new file mode 100644 index 000000000..39832856b --- /dev/null +++ b/script/core/command/exportDocument.lua @@ -0,0 +1,16 @@ +local doc = require 'cli.doc' +local client = require 'client' +local furi = require 'file-uri' +local lang = require 'language' +local ws = require 'workspace' +local files = require 'files' + +---@async +return function (args) + local outputPath = args[1] and furi.decode(args[1]) or LOGPATH + local docPath, mdPath = doc.makeDoc(outputPath) + client.showMessage('Info', lang.script('CLI_DOC_DONE' + , ('[%s](%s)'):format(files.normalize(docPath), furi.encode(docPath)) + , ('[%s](%s)'):format(files.normalize(mdPath), furi.encode(mdPath)) + )) +end diff --git a/script/core/command/getConfig.lua b/script/core/command/getConfig.lua new file mode 100644 index 000000000..7a35c198e --- /dev/null +++ b/script/core/command/getConfig.lua @@ -0,0 +1,13 @@ +local config = require 'config' +local client = require 'client' +local await = require 'await' + +---@async +return function (data) + local uri = data[1].uri + local key = data[1].key + while not client:isReady() do + await.sleep(0.1) + end + return config.get(uri, key) +end diff --git a/script/core/command/jsonToLua.lua b/script/core/command/jsonToLua.lua index fb0fc6c95..f09a3681c 100644 --- a/script/core/command/jsonToLua.lua +++ b/script/core/command/jsonToLua.lua @@ -1,38 +1,50 @@ local files = require 'files' -local json = require 'json' local util = require 'utility' local proto = require 'proto' local define = require 'proto.define' local lang = require 'language' local converter = require 'proto.converter' local guide = require 'parser.guide' +local json = require 'json' +local jsonc = require 'jsonc' ---@async return function (data) local state = files.getState(data.uri) local text = files.getText(data.uri) - if not text then + if not text or not state then return end local start = guide.positionToOffset(state, data.start) local finish = guide.positionToOffset(state, data.finish) local jsonStr = text:sub(start + 1, finish) - local suc, res = pcall(json.decode, jsonStr) - if not suc then + local suc, res = pcall(jsonc.decode_jsonc, jsonStr:match '[%{%[].+') + if not suc or res == json.null then proto.notify('window/showMessage', { type = define.MessageType.Warning, message = lang.script('COMMAND_JSON_TO_LUA_FAILED', res:match '%:%d+%:(.+)'), }) return end + ---@cast res table local luaStr = util.dump(res) + if jsonStr:sub(1, 1) == '"' then + local key = jsonStr:match '^"([^\r\n]+)"' + if key then + if key:match '^[%a_]%w*$' then + luaStr = ('%s = %s'):format(key, luaStr) + else + luaStr = ('[%q] = %s'):format(key, luaStr) + end + end + end proto.awaitRequest('workspace/applyEdit', { label = 'json to lua', edit = { changes = { [data.uri] = { { - range = converter.packRange(data.uri, data.start, data.finish), + range = converter.packRange(state, data.start, data.finish), newText = luaStr, } } diff --git a/script/core/command/reloadFFIMeta.lua b/script/core/command/reloadFFIMeta.lua new file mode 100644 index 000000000..d00929adb --- /dev/null +++ b/script/core/command/reloadFFIMeta.lua @@ -0,0 +1,56 @@ +local config = require 'config' +local ws = require 'workspace' +local fs = require 'bee.filesystem' +local scope = require 'workspace.scope' +local SDBMHash = require 'SDBMHash' +local searchCode = require 'plugins.ffi.searchCode' +local cdefRerence = require 'plugins.ffi.cdefRerence' +local ffi = require 'plugins.ffi' + +local function createDir(uri) + local dir = scope.getScope(uri).uri or 'default' + local fileDir = fs.path(METAPATH) / ('%08x'):format(SDBMHash():hash(dir)) + if fs.exists(fileDir) then + return fileDir, true + end + fs.create_directories(fileDir) + return fileDir +end + +---@async +return function (uri) + if config.get(uri, 'Lua.runtime.version') ~= 'LuaJIT' then + return + end + + ws.awaitReady(uri) + + local fileDir, exists = createDir(uri) + + local refs = cdefRerence() + if not refs or #refs == 0 then + return + end + + for i, v in ipairs(refs) do + local target_uri = v.uri + local codes = searchCode(refs, target_uri) + if not codes then + return + end + + ffi.build_single(codes, fileDir, target_uri) + end + + if not exists then + local client = require 'client' + client.setConfig { + { + key = 'Lua.workspace.library', + action = 'add', + value = tostring(fileDir), + uri = uri, + } + } + end +end diff --git a/script/core/command/removeSpace.lua b/script/core/command/removeSpace.lua index 992a07057..87d11fffe 100644 --- a/script/core/command/removeSpace.lua +++ b/script/core/command/removeSpace.lua @@ -38,7 +38,7 @@ return function (data) end local firstPos = guide.offsetToPosition(state, firstOffset) - 1 textEdit[#textEdit+1] = { - range = converter.packRange(uri, firstPos, lastPos), + range = converter.packRange(state, firstPos, lastPos), newText = '', } diff --git a/script/core/command/setConfig.lua b/script/core/command/setConfig.lua index 4587dd479..af978ebe9 100644 --- a/script/core/command/setConfig.lua +++ b/script/core/command/setConfig.lua @@ -1,5 +1,11 @@ local client = require 'client' +local await = require 'await' -return function (data) - client.setConfig { data } +---@async +---@param changes config.change[] +return function (changes) + while not client:isReady() do + await.sleep(0.1) + end + client.setConfig(changes) end diff --git a/script/core/command/solve.lua b/script/core/command/solve.lua index 98ceaa589..ca1458aad 100644 --- a/script/core/command/solve.lua +++ b/script/core/command/solve.lua @@ -36,7 +36,7 @@ return function (data) return end - local start, finish = converter.unpackRange(uri, data.range) + local start, finish = converter.unpackRange(state, data.range) local result = guide.eachSourceContain(state.ast, start, function (source) if source.start ~= start @@ -86,7 +86,7 @@ return function (data) changes = { [uri] = { { - range = converter.packRange(uri, result.start, result.finish), + range = converter.packRange(state, result.start, result.finish), newText = ('(%s)'):format(text:sub( guide.positionToOffset(state, result.start + 1), guide.positionToOffset(state, result.finish) diff --git a/script/core/completion/auto-require.lua b/script/core/completion/auto-require.lua new file mode 100644 index 000000000..3139b9116 --- /dev/null +++ b/script/core/completion/auto-require.lua @@ -0,0 +1,109 @@ +local config = require 'config' +local util = require 'utility' +local guide = require 'parser.guide' +local workspace = require 'workspace' +local files = require 'files' +local furi = require 'file-uri' +local rpath = require 'workspace.require-path' +local vm = require 'vm' +local matchKey = require 'core.matchkey' + +---@class auto-require +local m = {} + +---@type table +m.validUris = {} + +---@param state parser.state +---@return parser.object? +function m.getTargetSource(state) + local targetReturns = state.ast.returns + if not targetReturns then + return nil + end + local targetSource = targetReturns[1] and targetReturns[1][1] + if not targetSource then + return nil + end + if targetSource.type ~= 'getlocal' + and targetSource.type ~= 'table' + and targetSource.type ~= 'function' then + return nil + end + return targetSource +end + +function m.check(state, word, position, callback) + local globals = util.arrayToHash(config.get(state.uri, 'Lua.diagnostics.globals')) + local locals = guide.getVisibleLocals(state.ast, position) + for uri in files.eachFile(state.uri) do + if uri == guide.getUri(state.ast) then + goto CONTINUE + end + if not m.validUris[uri] then + goto CONTINUE + end + local path = furi.decode(uri) + local relativePath = workspace.getRelativePath(path) + local infos = rpath.getVisiblePath(uri, path) + local testedStem = { } + for _, sr in ipairs(infos) do + local stemName + if sr.searcher == '[[meta]]' then + stemName = sr.name + else + local pattern = sr.searcher + : gsub("(%p)", "%%%1") + : gsub("%%%?", "(.-)") + + local stemPath = relativePath:match(pattern) + if not stemPath then + goto INNER_CONTINUE + end + + stemName = stemPath:match("[%a_][%w_]*$") + + if not stemName or testedStem[stemName] then + goto INNER_CONTINUE + end + end + testedStem[stemName] = true + + if not locals[stemName] + and not vm.hasGlobalSets(state.uri, 'variable', stemName) + and not globals[stemName] + and matchKey(word, stemName) then + local targetState = files.getState(uri) + if not targetState then + goto INNER_CONTINUE + end + local targetSource = m.getTargetSource(targetState) + if not targetSource then + goto INNER_CONTINUE + end + if targetSource.type == 'getlocal' + and vm.getDeprecated(targetSource.node) then + goto INNER_CONTINUE + end + callback(uri, stemName, targetSource) + end + ::INNER_CONTINUE:: + end + ::CONTINUE:: + end +end + +files.watch(function (ev, uri) + if ev == 'update' + or ev == 'remove' then + m.validUris[uri] = nil + end + if ev == 'compile' then + local state = files.getLastState(uri) + if state and m.getTargetSource(state) then + m.validUris[uri] = true + end + end +end) + +return m diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index d6052c7ae..f9e50563e 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -19,6 +19,10 @@ local guide = require 'parser.guide' local await = require 'await' local postfix = require 'core.completion.postfix' local diag = require 'proto.diagnostic' +local wssymbol = require 'core.workspace-symbol' +local findSource = require 'core.find-source' +local diagnostic = require 'provider.diagnostic' +local autoRequire = require 'core.completion.auto-require' local diagnosticModes = { 'disable-next-line', @@ -30,10 +34,24 @@ local diagnosticModes = { local stackID = 0 local stacks = {} ----@param callback async fun() -local function stack(callback) +---@param callback async fun(newSource: parser.object): table +local function stack(oldSource, callback) stackID = stackID + 1 - stacks[stackID] = callback + local uri = guide.getUri(oldSource) + local pos = oldSource.start + local tp = oldSource.type + ---@async + stacks[stackID] = function () + local state = files.getState(uri) + if not state then + return + end + local newSource = findSource(state, pos, { [tp] = true }) + if not newSource then + return + end + return callback(newSource) + end return stackID end @@ -65,9 +83,9 @@ local function findNearestSource(state, position) return source end -local function findNearestTableField(state, position) - local uri = state.uri - local text = files.getText(uri) +local function findNearestTable(state, position) + local uri = state.uri + local text = files.getText(uri) if not text then return nil end @@ -83,18 +101,55 @@ local function findNearestTableField(state, position) local sposition = guide.offsetToPosition(state, soffset) local source guide.eachSourceContain(state.ast, sposition, function (src) - if src.type == 'table' - or src.type == 'tablefield' - or src.type == 'tableindex' - or src.type == 'tableexp' then + if src.type == 'table' then source = src end end) + + if not source then + return nil + end + + for _, field in ipairs(source) do + if field.start <= position and (field.range or field.finish) >= position then + if field.type == 'tableexp' then + if field.value.type == 'getlocal' + or field.value.type == 'getglobal' then + if field.finish >= position then + return source + else + return nil + end + end + end + if field.type == 'tablefield' then + if field.finish >= position then + return source + else + return nil + end + end + if field.type == 'tableindex' then + if field.index and field.index.type == 'string' then + if field.index.finish >= position then + return source + else + return nil + end + end + end + return nil + end + end + return source end local function findParent(state, position) local text = state.lua + if not text then + return + end local offset = guide.positionToOffset(state, position) for i = offset, 1, -1 do local char = text:sub(i, i) @@ -244,10 +299,10 @@ local function buildFunction(results, source, value, oop, data) title = 'trigger signature', command = 'editor.action.triggerParameterHints', } - snipData.id = stack(function () ---@async + snipData.id = stack(source, function (newSource) ---@async return { - detail = buildDetail(source), - description = buildDesc(source), + detail = buildDetail(newSource), + description = buildDesc(newSource), } end) @@ -315,7 +370,7 @@ local function checkLocal(state, word, position, results) return orders[a] < orders[b] end) for _, def in ipairs(defs) do - if def.type == 'function' + if (def.type == 'function' and not vm.isVarargFunctionWithOverloads(def)) or def.type == 'doc.type.function' then local funcLabel = name .. getParams(def, false) buildFunction(results, source, def, false, { @@ -323,10 +378,10 @@ local function checkLocal(state, word, position, results) match = name, insertText = name, kind = define.CompletionItemKind.Function, - id = stack(function () ---@async + id = stack(source, function (newSource) ---@async return { - detail = buildDetail(source), - description = buildDesc(source), + detail = buildDetail(newSource), + description = buildDesc(newSource), } end), }) @@ -336,10 +391,10 @@ local function checkLocal(state, word, position, results) results[#results+1] = { label = name, kind = define.CompletionItemKind.Variable, - id = stack(function () ---@async + id = stack(source, function (newSource) ---@async return { - detail = buildDetail(source), - description = buildDesc(source), + detail = buildDetail(newSource), + description = buildDesc(newSource), } end), } @@ -352,78 +407,46 @@ local function checkModule(state, word, position, results) if not config.get(state.uri, 'Lua.completion.autoRequire') then return end - local globals = util.arrayToHash(config.get(state.uri, 'Lua.diagnostics.globals')) - local locals = guide.getVisibleLocals(state.ast, position) - for uri in files.eachFile(state.uri) do - if uri == guide.getUri(state.ast) then - goto CONTINUE - end - local path = furi.decode(uri) - local fileName = path:match '[^/\\]*$' - local stemName = fileName:gsub('%..+', '') - if not locals[stemName] - and not vm.hasGlobalSets(state.uri, 'variable', stemName) - and not globals[stemName] - and stemName:match '^[%a_][%w_]*$' - and matchKey(word, stemName) then - local targetState = files.getState(uri) - if not targetState then - goto CONTINUE - end - local targetReturns = targetState.ast.returns - if not targetReturns then - goto CONTINUE - end - local targetSource = targetReturns[1] and targetReturns[1][1] - if not targetSource then - goto CONTINUE - end - if targetSource.type ~= 'getlocal' - and targetSource.type ~= 'table' - and targetSource.type ~= 'function' then - goto CONTINUE - end - if targetSource.type == 'getlocal' - and vm.getDeprecated(targetSource.node) then - goto CONTINUE - end - results[#results+1] = { - label = stemName, - kind = define.CompletionItemKind.Variable, - commitCharacters = { '.' }, - command = { - title = 'autoRequire', - command = 'lua.autoRequire', - arguments = { - { - uri = guide.getUri(state.ast), - target = uri, - name = stemName, - }, + autoRequire.check(state, word, position, function (uri, stemName, targetSource) + results[#results+1] = { + label = stemName, + kind = define.CompletionItemKind.Variable, + commitCharacters = { '.' }, + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = guide.getUri(state.ast), + target = uri, + name = stemName, }, }, - id = stack(function () ---@async - local md = markdown() - md:add('md', lang.script('COMPLETION_IMPORT_FROM', ('[%s](%s)'):format( - workspace.getRelativePath(uri), - uri - ))) - md:add('md', buildDesc(targetSource)) - return { - detail = buildDetail(targetSource), - description = md, - --additionalTextEdits = buildInsertRequire(state, originUri, stemName), - } - end) - } - end - ::CONTINUE:: - end + }, + id = stack(targetSource, function (newSource) ---@async + local md = markdown() + md:add('md', lang.script('COMPLETION_IMPORT_FROM', ('[%s](%s)'):format( + workspace.getRelativePath(uri), + uri + ))) + md:add('md', buildDesc(newSource)) + return { + detail = buildDetail(newSource), + description = md, + --additionalTextEdits = buildInsertRequire(state, originUri, stemName), + } + end) + } + end) end local function checkFieldFromFieldToIndex(state, name, src, parent, word, startPos, position) - if name:match '^[%a_][%w_]*$' then - return nil + if name:match(guide.namePatternFull) then + if not name:match '[\x80-\xff]' + or config.get(state.uri, 'Lua.runtime.unicodeName') then + return nil + end + name = ('%q'):format(name) end local textEdit, additionalTextEdits local startOffset = guide.positionToOffset(state, startPos) @@ -440,12 +463,7 @@ local function checkFieldFromFieldToIndex(state, name, src, parent, word, startP wordStartOffset = offset - #word end local wordStartPos = guide.offsetToPosition(state, wordStartOffset) - local newText - if vm.getKeyType(src) == 'string' then - newText = ('[%q]'):format(name) - else - newText = ('[%s]'):format(name) - end + local newText = ('[%s]'):format(name) textEdit = { start = wordStartPos, finish = position, @@ -474,8 +492,8 @@ local function checkFieldFromFieldToIndex(state, name, src, parent, word, startP } end else - if config.get(state.uri, 'Lua.runtime.version') == 'lua 5.1' - or config.get(state.uri, 'Lua.runtime.version') == 'luaJIT' then + if config.get(state.uri, 'Lua.runtime.version') == 'Lua 5.1' + or config.get(state.uri, 'Lua.runtime.version') == 'LuaJIT' then textEdit.newText = '_G' .. textEdit.newText else textEdit.newText = '_ENV' .. textEdit.newText @@ -487,9 +505,10 @@ end local function checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) local value = vm.getObjectFunctionValue(src) or src local kind = define.CompletionItemKind.Field - if value.type == 'function' + if (value.type == 'function' and not vm.isVarargFunctionWithOverloads(value)) or value.type == 'doc.type.function' then - if oop then + local isMethod = value.parent.type == 'setmethod' + if isMethod then kind = define.CompletionItemKind.Method else kind = define.CompletionItemKind.Function @@ -497,13 +516,14 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent buildFunction(results, src, value, oop, { label = name, kind = kind, + isMethod = isMethod, match = name:match '^[^(]+', insertText = name:match '^[^(]+', deprecated = vm.getDeprecated(src) and true or nil, - id = stack(function () ---@async + id = stack(src, function (newSrc) ---@async return { - detail = buildDetail(src), - description = buildDesc(src), + detail = buildDetail(newSrc), + description = buildDesc(newSrc), } end), }) @@ -522,7 +542,7 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent textEdit = { start = str.start + #str[2], finish = position, - newText = name, + newText = name:sub(#str[2] + 1, - #str[2] - 1), } else textEdit, additionalTextEdits = checkFieldFromFieldToIndex(state, name, src, parent, word, startPos, position) @@ -532,10 +552,10 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent kind = kind, deprecated = vm.getDeprecated(src) and true or nil, textEdit = textEdit, - id = stack(function () ---@async + id = stack(src, function (newSrc) ---@async return { - detail = buildDetail(src), - description = buildDesc(src), + detail = buildDetail(newSrc), + description = buildDesc(newSrc), } end), @@ -549,7 +569,11 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o local funcs = {} local count = 0 for _, src in ipairs(refs) do - local name = vm.getKeyName(src) + if count > 100 then + results.incomplete = true + break + end + local _, name = vm.viewKey(src, state.uri) if not name then goto CONTINUE end @@ -560,7 +584,10 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o if isGlobal and locals and locals[name] then goto CONTINUE end - if not matchKey(word, name, count >= 100) then + if not matchKey(word, name:gsub([=[^['"]]=], ''), count >= 100) then + goto CONTINUE + end + if not vm.isVisible(parent, src) then goto CONTINUE end local funcLabel @@ -569,9 +596,11 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o local value = vm.getObjectFunctionValue(src) or src if value.type == 'function' or value.type == 'doc.type.function' then - funcLabel = name .. getParams(value, oop) - fields[funcLabel] = src - count = count + 1 + if not vm.isVarargFunctionWithOverloads(value) then + funcLabel = name .. getParams(value, oop) + fields[funcLabel] = src + count = count + 1 + end if value.type == 'function' and value.bindDocs then for _, doc in ipairs(value.bindDocs) do if doc.type == 'doc.overload' then @@ -581,7 +610,7 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o end end funcs[name] = true - if fields[name] and not guide.isSet(fields[name]) then + if fields[name] and not guide.isAssign(fields[name]) then fields[name] = nil end goto CONTINUE @@ -596,18 +625,49 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o if vm.getDeprecated(src) then goto CONTINUE end - if guide.isSet(src) then + if guide.isAssign(src) then fields[name] = src goto CONTINUE end ::CONTINUE:: end + + local fieldResults = {} for name, src in util.sortPairs(fields) do if src then - checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) + checkFieldThen(state, name, src, word, startPos, position, parent, oop, fieldResults) await.delay() end end + + local scoreMap = {} + for i, res in ipairs(fieldResults) do + scoreMap[res] = i + end + table.sort(fieldResults, function (a, b) + local score1 = scoreMap[a] + local score2 = scoreMap[b] + if oop then + if not a.isMethod then + score1 = score1 + 10000 + end + if not b.isMethod then + score2 = score2 + 10000 + end + else + if a.isMethod then + score1 = score1 + 10000 + end + if b.isMethod then + score2 = score2 + 10000 + end + end + return score1 < score2 + end) + + for _, res in ipairs(fieldResults) do + results[#results+1] = res + end end ---@async @@ -618,6 +678,7 @@ local function checkGlobal(state, word, startPos, position, parent, oop, results end ---@async +---@param parent parser.object local function checkField(state, word, start, position, parent, oop, results) if parent.tag == '_ENV' or parent.special == '_G' then local globals = vm.getGlobalSets(state.uri, 'variable') @@ -726,7 +787,7 @@ local function checkCommon(state, word, position, results) end end end - for str, offset in state.lua:gmatch '([%a_][%w_]+)()' do + for str, offset in state.lua:gmatch('(' .. guide.namePattern .. ')()') do if #results >= 100 then results.incomplete = true break @@ -749,7 +810,7 @@ local function checkKeyWord(state, start, position, word, hasSpace, afterLocal, local text = state.lua local snipType = config.get(state.uri, 'Lua.completion.keywordSnippet') local symbol = lookBackward.findSymbol(text, guide.positionToOffset(state, start)) - local isExp = symbol == '(' or symbol == ',' or symbol == '=' or symbol == '[' + local isExp = symbol == '(' or symbol == ',' or symbol == '=' or symbol == '[' or symbol == '{' local info = { hasSpace = hasSpace, isExp = isExp, @@ -898,8 +959,7 @@ local function checkFunctionArgByDocParam(state, word, startPos, results) end end -local function isAfterLocal(state, startPos) - local text = state.lua +local function isAfterLocal(state, text, startPos) local offset = guide.positionToOffset(state, startPos) local pos = lookBackward.skipSpace(text, offset) local word = lookBackward.findWord(text, pos) @@ -908,6 +968,8 @@ end local function collectRequireNames(mode, myUri, literal, source, smark, position, results) local collect = {} + local source_start = source and smark and (source.start + #smark) or position + local source_finish = source and smark and (source.finish - #smark) or position if mode == 'require' then for uri in files.eachFile(myUri) do if myUri == uri then @@ -921,8 +983,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[info.name] then collect[info.name] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and info.name or util.viewString(info.name), }, path = relative, @@ -949,8 +1011,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[open] then collect[open] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and open or util.viewString(open), }, path = path, @@ -977,8 +1039,8 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position if not collect[path] then collect[path] = { textEdit = { - start = smark and (source.start + #smark) or position, - finish = smark and (source.finish - #smark) or position, + start = source_start, + finish = source_finish, newText = smark and path or util.viewString(path), } } @@ -1040,6 +1102,9 @@ end local function checkLenPlusOne(state, position, results) local text = state.lua + if not text then + return + end guide.eachSourceContain(state.ast, position, function (source) if source.type == 'getindex' or source.type == 'setindex' then @@ -1134,8 +1199,8 @@ local function insertDocEnum(state, pos, doc, enums) end local parent = tbl.parent local parentName - if parent._globalNode then - parentName = parent._globalNode:getName() + if vm.getGlobalNode(parent) then + parentName = vm.getGlobalNode(parent):getCodeName() else local locals = guide.getVisibleLocals(state.ast, pos) for _, loc in pairs(locals) do @@ -1156,30 +1221,34 @@ local function insertDocEnum(state, pos, doc, enums) if not key then goto CONTINUE end - if field.value.type == 'integer' - or field.value.type == 'string' then - if parentName then - enums[#enums+1] = { - label = parentName .. '.' .. key, + if parentName then + enums[#enums+1] = { + label = parentName .. '.' .. key, + kind = define.CompletionItemKind.EnumMember, + id = stack(field, function (newField) ---@async + return { + detail = buildDetail(newField), + description = buildDesc(newField), + } + end), + } + end + for nd in vm.compileNode(field.value):eachObject() do + if nd.type == 'boolean' + or nd.type == 'number' + or nd.type == 'integer' + or nd.type == 'string' then + valueEnums[#valueEnums+1] = { + label = util.viewLiteral(nd[1]), kind = define.CompletionItemKind.EnumMember, - id = stack(function () ---@async + id = stack(field, function (newField) ---@async return { - detail = buildDetail(field), - description = buildDesc(field), + detail = buildDetail(newField), + description = buildDesc(newField), } end), } end - valueEnums[#valueEnums+1] = { - label = util.viewLiteral(field.value[1]), - kind = define.CompletionItemKind.EnumMember, - id = stack(function () ---@async - return { - detail = buildDetail(field), - description = buildDesc(field), - } - end), - } end ::CONTINUE:: end @@ -1190,18 +1259,75 @@ local function insertDocEnum(state, pos, doc, enums) return enums end +---@param state parser.state +---@param pos integer +---@param doc vm.node.object +---@param enums table[] +---@return table[]? +local function insertDocEnumKey(state, pos, doc, enums) + local tbl = doc.bindSource + if not tbl then + return nil + end + local keyEnums = {} + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE + end + enums[#enums+1] = { + label = ('%q'):format(key), + kind = define.CompletionItemKind.EnumMember, + id = stack(field, function (newField) ---@async + return { + detail = buildDetail(newField), + description = buildDesc(newField), + } + end), + } + ::CONTINUE:: + end + end + for _, enum in ipairs(keyEnums) do + enums[#enums+1] = enum + end + return enums +end + +local function buildInsertDocFunction(doc) + local args = {} + for i, arg in ipairs(doc.args) do + args[i] = ('${%d:%s}'):format(i, arg.name[1]) + end + return ("\z +function (%s)\ +\t$0\ +end"):format(table.concat(args, ', ')) +end + ---@param state parser.state ---@param pos integer ---@param src vm.node.object ---@param enums table[] ---@param isInArray boolean? -local function insertEnum(state, pos, src, enums, isInArray) +---@param mark table? +local function insertEnum(state, pos, src, enums, isInArray, mark) + mark = mark or {} + if mark[src] then + return + end + mark[src] = true if src.type == 'doc.type.string' or src.type == 'doc.type.integer' or src.type == 'doc.type.boolean' then ---@cast src parser.object enums[#enums+1] = { - label = vm.viewObject(src, state.uri), + label = vm.getInfer(src):view(state.uri), description = src.comment, kind = define.CompletionItemKind.EnumMember, } @@ -1211,14 +1337,38 @@ local function insertEnum(state, pos, src, enums, isInArray) description = src.comment, kind = define.CompletionItemKind.EnumMember, } + elseif src.type == 'doc.type.function' then + ---@cast src parser.object + 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 = vm.getInfer(src):view(state.uri), + description = description, + kind = define.CompletionItemKind.Function, + insertText = insertText, + } elseif isInArray and src.type == 'doc.type.array' then for i, d in ipairs(vm.getDefs(src.node)) do - insertEnum(state, pos, d, enums, isInArray) + insertEnum(state, pos, d, enums, isInArray, mark) end elseif src.type == 'global' and src.cate == 'type' then for _, set in ipairs(src:getSets(state.uri)) do if set.type == 'doc.enum' then - insertDocEnum(state, pos, set, enums) + if vm.docHasAttr(set, 'key') then + insertDocEnumKey(state, pos, set, enums) + else + insertDocEnum(state, pos, set, enums) + end end end end @@ -1250,6 +1400,9 @@ end local function checkEqualEnum(state, position, results) local text = state.lua + if not text then + return + end local start = lookBackward.findTargetSymbol(text, guide.positionToOffset(state, position), '=') if not start then return @@ -1351,6 +1504,9 @@ local function tryWord(state, position, triggerCharacter, results) return end local text = state.lua + if not text then + return + end local offset = guide.positionToOffset(state, position) local finish = lookBackward.skipSpace(text, offset) local word, start = lookBackward.findWord(text, offset) @@ -1376,7 +1532,7 @@ local function tryWord(state, position, triggerCharacter, results) checkProvideLocal(state, word, startPos, results) checkFunctionArgByDocParam(state, word, startPos, results) else - local afterLocal = isAfterLocal(state, startPos) + local afterLocal = isAfterLocal(state, text, startPos) local stop = checkKeyWord(state, startPos, position, word, hasSpace, afterLocal, results) if stop then return @@ -1388,8 +1544,10 @@ local function tryWord(state, position, triggerCharacter, results) checkLocal(state, word, startPos, results) checkTableField(state, word, startPos, results) local env = guide.getENV(state.ast, startPos) - checkGlobal(state, word, startPos, position, env, false, results) - checkModule(state, word, startPos, results) + if env then + checkGlobal(state, word, startPos, position, env, false, results) + checkModule(state, word, startPos, results) + end end end end @@ -1424,17 +1582,6 @@ local function trySymbol(state, position, results) end end -local function buildInsertDocFunction(doc) - local args = {} - for i, arg in ipairs(doc.args) do - args[i] = ('${%d:%s}'):format(i, arg.name[1]) - end - return ("\z -function (%s)\ -\t$0\ -end"):format(table.concat(args, ', ')) -end - local function findCall(state, position) local call guide.eachSourceContain(state.ast, position, function (src) @@ -1461,6 +1608,9 @@ end local function checkTableLiteralField(state, position, tbl, fields, results) local text = state.lua + if not text then + return + end local mark = {} for _, field in ipairs(tbl) do if field.type == 'tablefield' @@ -1479,29 +1629,44 @@ local function checkTableLiteralField(state, position, tbl, fields, results) local left = lookBackward.findWord(text, guide.positionToOffset(state, position)) if not left then local pos = lookBackward.findAnyOffset(text, guide.positionToOffset(state, position)) - local char = text:sub(pos, pos) - if char == '{' or char == ',' or char == ';' then - left = '' + if pos then + local char = text:sub(pos, pos) + if char == '{' or char == ',' or char == ';' then + left = '' + end end end if left then + local fieldResults = {} for _, field in ipairs(fields) do local name = guide.getKeyName(field) if name and not mark[name] and matchKey(left, tostring(name)) then - results[#results+1] = { + local res = { label = guide.getKeyName(field), kind = define.CompletionItemKind.Property, - id = stack(function () ---@async + id = stack(field, function (newField) ---@async return { - detail = buildDetail(field), - description = buildDesc(field), + detail = buildDetail(newField), + description = buildDesc(newField), } end), } + if field.optional + or vm.compileNode(field):isNullable() then + res.insertText = res.label + res.label = res.label.. '?' + end + fieldResults[#fieldResults+1] = res end end + util.sortByScore(fieldResults, { + function (r) return r.insertText and 0 or 1 end, + util.sortCallbackOfIndex(fieldResults), + }) + util.arrayMerge(results, fieldResults) + return #fieldResults > 0 end end @@ -1514,7 +1679,8 @@ local function tryCallArg(state, position, results) if arg and arg.type == 'function' then return end - local node = vm.compileCallArg({ type = 'dummyarg' }, call, argIndex) + ---@diagnostic disable-next-line: missing-fields + local node = vm.compileCallArg({ type = 'dummyarg', uri = state.uri }, call, argIndex) if not node then return end @@ -1522,27 +1688,6 @@ local function tryCallArg(state, position, results) local enums = {} for src in node:eachObject() do insertEnum(state, position, src, enums, arg and arg.type == 'table') - if src.type == 'doc.type.function' then - ---@cast src parser.object - 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 = vm.getInfer(src):view(state.uri), - description = description, - kind = define.CompletionItemKind.Function, - insertText = insertText, - } - end end cleanEnums(enums, arg) for _, enum in ipairs(enums) do @@ -1551,20 +1696,15 @@ local function tryCallArg(state, position, results) end local function tryTable(state, position, results) - local source = findNearestTableField(state, position) - if not source then - return + local tbl = findNearestTable(state, position) + if not tbl then + return false end - if source.type ~= 'table' - and (not source.parent or source.parent.type ~= 'table') then + if tbl.type ~= 'table' then return end local mark = {} local fields = {} - local tbl = source - if source.type ~= 'table' then - tbl = source.parent - end local defs = vm.getFields(tbl) for _, field in ipairs(defs) do @@ -1574,7 +1714,10 @@ local function tryTable(state, position, results) fields[#fields+1] = field end end - checkTableLiteralField(state, position, tbl, fields, results) + if checkTableLiteralField(state, position, tbl, fields, results) then + return true + end + return false end local function tryArray(state, position, results) @@ -1651,6 +1794,9 @@ local function tryluaDocCate(word, results) 'operator', 'source', 'enum', + 'package', + 'private', + 'protected' } do if matchKey(word, docType) then results[#results+1] = { @@ -1678,6 +1824,7 @@ local function getluaDocByContain(state, position) return result end +---@return parser.state.err?, parser.object? local function getluaDocByErr(state, start, position) local targetError for _, err in ipairs(state.errs) do @@ -1703,6 +1850,7 @@ local function getluaDocByErr(state, start, position) return targetError, targetDoc end +---@async local function tryluaDocBySource(state, position, source, results) if source.type == 'doc.extends.name' then if source.parent.type == 'doc.class' then @@ -1811,11 +1959,11 @@ local function tryluaDocBySource(state, position, source, results) if matchKey(source[1], name) then results[#results+1] = { label = name, - kind = define.CompletionItemKind.Variable, - id = stack(function () ---@async + kind = define.CompletionItemKind.Variable, + id = stack(loc, function (newLoc) ---@async return { - detail = buildDetail(loc), - description = buildDesc(loc), + detail = buildDetail(newLoc), + description = buildDesc(newLoc), } end), } @@ -1851,17 +1999,40 @@ local function tryluaDocBySource(state, position, source, results) end end return true + elseif source.type == 'doc.see.name' then + local symbolds = wssymbol(source[1], state.uri) + table.sort(symbolds, function (a, b) + return a.name < b.name + end) + for _, symbol in ipairs(symbolds) do + results[#results+1] = { + label = symbol.name, + kind = symbol.ckind, + id = stack(symbol.source, function (newSource) ---@async + return { + detail = buildDetail(newSource), + description = buildDesc(newSource), + } + end), + textEdit = { + start = source.start, + finish = source.finish, + newText = symbol.name, + }, + } + end end return false end +---@async local function tryluaDocByErr(state, position, err, docState, results) if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' 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 + and docState and doc.class[1] ~= docState.class[1] then used[doc.class[1]] = true results[#results+1] = { label = doc.class[1], @@ -1960,10 +2131,10 @@ local function tryluaDocByErr(state, position, err, docState, results) results[#results+1] = { label = name, kind = define.CompletionItemKind.Variable, - id = stack(function () ---@async + id = stack(loc, function (newLoc) ---@async return { - detail = buildDetail(loc), - description = buildDesc(loc), + detail = buildDetail(newLoc), + description = buildDesc(newLoc), } end), } @@ -1991,10 +2162,27 @@ local function tryluaDocByErr(state, position, err, docState, results) description = ('```lua\n%s\n```'):format(vm.OP_OTHER_MAP[name]), } end + elseif err.type == 'LUADOC_MISS_SEE_NAME' then + local symbolds = wssymbol('', state.uri) + table.sort(symbolds, function (a, b) + return a.name < b.name + end) + for _, symbol in ipairs(symbolds) do + results[#results+1] = { + label = symbol.name, + kind = symbol.ckind, + id = stack(symbol.source, function (newSource) ---@async + return { + detail = buildDetail(newSource), + description = buildDesc(newSource), + } + end), + } + end end end -local function buildluaDocOfFunction(func) +local function buildluaDocOfFunction(func, pad) local index = 1 local buf = {} buf[#buf+1] = '${1:comment}' @@ -2018,7 +2206,8 @@ local function buildluaDocOfFunction(func) local funcArg = func.args[n] if funcArg[1] and funcArg.type ~= 'self' then index = index + 1 - buf[#buf+1] = ('---@param %s ${%d:%s}'):format( + buf[#buf+1] = ('---%s@param %s ${%d:%s}'):format( + pad and ' ' or '', funcArg[1], index, arg @@ -2027,7 +2216,8 @@ local function buildluaDocOfFunction(func) end for _, rtn in ipairs(returns) do index = index + 1 - buf[#buf+1] = ('---@return ${%d:%s}'):format( + buf[#buf+1] = ('---%s@return ${%d:%s}'):format( + pad and ' ' or '', index, rtn ) @@ -2036,7 +2226,7 @@ local function buildluaDocOfFunction(func) return insertText end -local function tryluaDocOfFunction(doc, results) +local function tryluaDocOfFunction(doc, results, pad) if not doc.bindSource then return end @@ -2058,7 +2248,7 @@ local function tryluaDocOfFunction(doc, results) end end end - local insertText = buildluaDocOfFunction(func) + local insertText = buildluaDocOfFunction(func, pad) results[#results+1] = { label = '@param;@return', kind = define.CompletionItemKind.Snippet, @@ -2068,6 +2258,7 @@ local function tryluaDocOfFunction(doc, results) } end +---@async local function tryLuaDoc(state, position, results) local doc = getLuaDoc(state, position) if not doc then @@ -2075,9 +2266,9 @@ local function tryLuaDoc(state, position, results) end if doc.type == 'doc.comment' then local line = doc.originalComment.text - -- ๅฐ่ฏ• ---$ - if line == '-' then - tryluaDocOfFunction(doc, results) + -- ๅฐ่ฏ• '---$' or '--- $' + if line == '-' or line == '- ' then + tryluaDocOfFunction(doc, results, line == '- ') return end -- ๅฐ่ฏ• ---@$ @@ -2137,15 +2328,6 @@ end ---@async local function tryCompletions(state, position, triggerCharacter, results) - local text = state.lua - if not state then - local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) - if not word then - return - end - checkCommon(nil, word, position, results) - return - end if getComment(state, position) then tryLuaDoc(state, position, results) tryComment(state, position, results) @@ -2154,9 +2336,11 @@ local function tryCompletions(state, position, triggerCharacter, results) if postfix(state, position, results) then return end + if tryTable(state, position, results) then + return + end trySpecial(state, position, results) tryCallArg(state, position, results) - tryTable(state, position, results) tryArray(state, position, results) tryWord(state, position, triggerCharacter, results) tryIndex(state, position, results) @@ -2170,6 +2354,8 @@ local function completion(uri, position, triggerCharacter) return nil end clearStack() + diagnostic.pause() + local _ = diagnostic.resume local results = {} tracy.ZoneBeginN 'completion #2' tryCompletions(state, position, triggerCharacter, results) diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua index 5558106a8..aa0e21487 100644 --- a/script/core/completion/keyword.lua +++ b/script/core/completion/keyword.lua @@ -1,6 +1,9 @@ local define = require 'proto.define' local files = require 'files' local guide = require 'parser.guide' +local config = require 'config' +local util = require 'utility' +local lookback = require 'core.look-backward' local keyWordMap = { { 'do', function(info, results) @@ -324,6 +327,84 @@ end" end return true end }, + { 'continue', function (info, results) + local nonstandardSymbol = config.get(info.uri, 'Lua.runtime.nonstandardSymbol') + if util.arrayHas(nonstandardSymbol, 'continue') then + return + end + local version = config.get(info.uri, 'Lua.runtime.version') + if version == 'Lua 5.1' then + return + end + local mostInsideBlock + guide.eachSourceContain(info.state.ast, info.start, function (src) + if src.type == 'while' + or src.type == 'in' + or src.type == 'loop' + or src.type == 'repeat' then + mostInsideBlock = src + end + end) + if not mostInsideBlock then + return + end + -- ๆ‰พไธ€ไธ‹ end ็š„ไฝ็ฝฎ + local endPos + if mostInsideBlock.type == 'while' then + endPos = mostInsideBlock.keyword[5] + elseif mostInsideBlock.type == 'in' then + endPos = mostInsideBlock.keyword[7] + elseif mostInsideBlock.type == 'loop' then + endPos = mostInsideBlock.keyword[5] + elseif mostInsideBlock.type == 'repeat' then + endPos = mostInsideBlock.keyword[3] + end + if not endPos then + return + end + local endLine = guide.rowColOf(endPos) + local tabStr = info.state.lua:sub( + info.state.lines[endLine], + guide.positionToOffset(info.state, endPos) + ) + local newText + if tabStr:match '^[\t ]*$' then + newText = ' ::continue::\n' .. tabStr + else + newText = '::continue::' + end + local additional = {} + + local word = lookback.findWord(info.state.lua, guide.positionToOffset(info.state, info.start) - 1) + if word ~= 'goto' then + additional[#additional+1] = { + start = info.start, + finish = info.start, + newText = 'goto ', + } + end + + local hasContinue = guide.eachSourceType(mostInsideBlock, 'label', function (src) + if src[1] == 'continue' then + return true + end + end) + + if not hasContinue then + additional[#additional+1] = { + start = endPos, + finish = endPos, + newText = newText, + } + end + results[#results+1] = { + label = 'goto continue ..', + kind = define.CompletionItemKind.Snippet, + insertText = "continue", + additionalTextEdits = additional, + } + return true + end } } return keyWordMap diff --git a/script/core/completion/postfix.lua b/script/core/completion/postfix.lua index c5988ef60..1331a0e4c 100644 --- a/script/core/completion/postfix.lua +++ b/script/core/completion/postfix.lua @@ -133,6 +133,39 @@ register 'xpcall' { end } +register 'ifcall' { + function (state, source, callback) + if source.type ~= 'getglobal' + and source.type ~= 'getfield' + and source.type ~= 'getmethod' + and source.type ~= 'getindex' + and source.type ~= 'getlocal' + and source.type ~= 'call' then + return + end + local subber = subString(state) + if source.type == 'call' then + if source.args and #source.args > 0 then + callback(string.format('if %s then %s(%s) end$0' + , subber(source.node.start + 1, source.node.finish) + , subber(source.node.start + 1, source.node.finish) + , subber(source.args[1].start + 1, source.args[#source.args].finish) + )) + else + callback(string.format('if %s then %s() end$0' + , subber(source.node.start + 1, source.node.finish) + , subber(source.node.start + 1, source.node.finish) + )) + end + else + callback(string.format('if %s then %s($1) end$0' + , subber(source.node.start + 1, source.node.finish) + , subber(source.node.start + 1, source.node.finish) + )) + end + end +} + register 'local' { function (state, source, callback) if source.type ~= 'getglobal' diff --git a/script/core/definition.lua b/script/core/definition.lua index 347d2cf98..844b17067 100644 --- a/script/core/definition.lua +++ b/script/core/definition.lua @@ -5,6 +5,7 @@ local findSource = require 'core.find-source' local guide = require 'parser.guide' local rpath = require 'workspace.require-path' local jumpSource = require 'core.jump-source' +local wssymbol = require 'core.workspace-symbol' local function sortResults(results) -- ๅ…ˆๆŒ‰็…ง้กบๅบๆŽ’ๅบ @@ -53,9 +54,9 @@ local accept = { ['doc.extends.name'] = true, ['doc.alias.name'] = true, ['doc.see.name'] = true, - ['doc.see.field'] = true, ['doc.cast.name'] = true, ['doc.enum.name'] = true, + ['doc.field.name'] = true, } local function checkRequire(source, offset) @@ -106,6 +107,26 @@ local function convertIndex(source) return source end +---@async +---@param source parser.object +---@param results table +local function checkSee(source, results) + if source.type ~= 'doc.see.name' then + return + end + local symbols = wssymbol(source[1], guide.getUri(source)) + for _, symbol in ipairs(symbols) do + if symbol.name == source[1] then + results[#results+1] = { + target = symbol.source, + source = source, + uri = guide.getUri(symbol.source), + } + end + end +end + +---@async return function (uri, offset) local ast = files.getState(uri) if not ast then @@ -133,6 +154,8 @@ return function (uri, offset) end end + checkSee(source, results) + local defs = vm.getDefs(source) for _, src in ipairs(defs) do @@ -174,12 +197,17 @@ return function (uri, offset) if src.type == 'doc.enum' then src = src.enum end + if src.type == 'doc.type.field' then + src = src.name + end if src.type == 'doc.class.name' or src.type == 'doc.alias.name' or src.type == 'doc.enum.name' then if source.type ~= 'doc.type.name' and source.type ~= 'doc.extends.name' - and source.type ~= 'doc.see.name' then + and source.type ~= 'doc.see.name' + and source.type ~= 'doc.class.name' + and source.type ~= 'doc.alias.name' then goto CONTINUE end end diff --git a/script/core/diagnostics/assign-type-mismatch.lua b/script/core/diagnostics/assign-type-mismatch.lua index 2d5c3f98e..8472e87c5 100644 --- a/script/core/diagnostics/assign-type-mismatch.lua +++ b/script/core/diagnostics/assign-type-mismatch.lua @@ -12,7 +12,8 @@ local checkTypes = { 'setindex', 'setmethod', 'tablefield', - 'tableindex' + 'tableindex', + 'tableexp', } ---@param source parser.object @@ -60,7 +61,7 @@ return function (uri, callback) await.delay() if source.type == 'setlocal' then local locNode = vm.compileNode(source.node) - if not locNode:getData 'hasDefined' then + if not locNode.hasDefined then return end end @@ -81,7 +82,8 @@ return function (uri, callback) end local valueNode = vm.compileNode(value) - if source.type == 'setindex' then + if source.type == 'setindex' + or source.type == 'tableexp' then -- boolean[1] = nil valueNode = valueNode:copy():removeOptional() end @@ -94,7 +96,8 @@ return function (uri, callback) end local varNode = vm.compileNode(source) - if vm.canCastType(uri, varNode, valueNode) then + local errs = {} + if vm.canCastType(uri, varNode, valueNode, errs) then return end @@ -111,7 +114,7 @@ return function (uri, callback) message = lang.script('DIAG_ASSIGN_TYPE_MISMATCH', { def = vm.getInfer(varNode):view(uri), ref = vm.getInfer(valueNode):view(uri), - }), + }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs), } end) end diff --git a/script/core/diagnostics/cast-local-type.lua b/script/core/diagnostics/cast-local-type.lua index c3d6e1bb3..264453745 100644 --- a/script/core/diagnostics/cast-local-type.lua +++ b/script/core/diagnostics/cast-local-type.lua @@ -16,9 +16,12 @@ return function (uri, callback) if not loc.ref then return end + if loc[1] == '_' then + return + end await.delay() local locNode = vm.compileNode(loc) - if not locNode:getData 'hasDefined' then + if not locNode.hasDefined then return end for _, ref in ipairs(loc.ref) do @@ -34,14 +37,15 @@ return function (uri, callback) refNode = refNode:copy():setTruthy() end - if not vm.canCastType(uri, locNode, refNode) then + local errs = {} + if not vm.canCastType(uri, locNode, refNode, errs) then callback { start = ref.start, finish = ref.finish, message = lang.script('DIAG_CAST_LOCAL_TYPE', { def = vm.getInfer(locNode):view(uri), ref = vm.getInfer(refNode):view(uri), - }), + }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs), } end end diff --git a/script/core/diagnostics/cast-type-mismatch.lua b/script/core/diagnostics/cast-type-mismatch.lua index a48e6ccab..d86443c57 100644 --- a/script/core/diagnostics/cast-type-mismatch.lua +++ b/script/core/diagnostics/cast-type-mismatch.lua @@ -16,24 +16,26 @@ return function (uri, callback) end for _, doc in ipairs(state.ast.docs) do - if doc.type == 'doc.cast' and doc.loc then + if doc.type == 'doc.cast' and doc.name then await.delay() - local defs = vm.getDefs(doc.loc) + local defs = vm.getDefs(doc.name) local loc = defs[1] if loc then local defNode = vm.compileNode(loc) - if defNode:getData 'hasDefined' then + if defNode.hasDefined then for _, cast in ipairs(doc.casts) do if not cast.mode and cast.extends then local refNode = vm.compileNode(cast.extends) - if not vm.canCastType(uri, defNode, refNode) then + local errs = {} + if not vm.canCastType(uri, defNode, refNode, errs) then + assert(errs) callback { start = cast.extends.start, finish = cast.extends.finish, message = lang.script('DIAG_CAST_TYPE_MISMATCH', { def = vm.getInfer(defNode):view(uri), ref = vm.getInfer(refNode):view(uri), - }) + }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs), } end end diff --git a/script/core/diagnostics/codestyle-check.lua b/script/core/diagnostics/codestyle-check.lua index 25603b4b4..6d22597b1 100644 --- a/script/core/diagnostics/codestyle-check.lua +++ b/script/core/diagnostics/codestyle-check.lua @@ -1,5 +1,4 @@ local files = require 'files' -local codeFormat = require 'code_format' local converter = require 'proto.converter' local log = require 'log' local pformatting = require 'provider.formatting' @@ -7,8 +6,14 @@ local pformatting = require 'provider.formatting' ---@async return function(uri, callback) - local text = files.getOriginText(uri) - if not text then + local state = files.getState(uri) + if not state then + return + end + local text = state.originText + + local suc, codeFormat = pcall(require, 'code_format') + if not suc then return end @@ -27,8 +32,8 @@ return function(uri, callback) if diagnosticInfos then for _, diagnosticInfo in ipairs(diagnosticInfos) do callback { - start = converter.unpackPosition(uri, diagnosticInfo.range.start), - finish = converter.unpackPosition(uri, diagnosticInfo.range["end"]), + start = converter.unpackPosition(state, diagnosticInfo.range.start), + finish = converter.unpackPosition(state, diagnosticInfo.range["end"]), message = diagnosticInfo.message } end diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua index 85ae2d196..6492bc53c 100644 --- a/script/core/diagnostics/deprecated.lua +++ b/script/core/diagnostics/deprecated.lua @@ -45,6 +45,9 @@ return function (uri, callback) local versions if deprecated.type == 'doc.version' then local validVersions = vm.getValidVersions(deprecated) + if not validVersions then + return + end versions = {} for version, valid in pairs(validVersions) do if valid then diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua index a30dfa883..098b41a48 100644 --- a/script/core/diagnostics/duplicate-doc-field.lua +++ b/script/core/diagnostics/duplicate-doc-field.lua @@ -2,6 +2,7 @@ local files = require 'files' local lang = require 'language' local vm = require 'vm.vm' local await = require 'await' +local guide = require 'parser.guide' local function getFieldEventName(doc) if not doc.extends then @@ -41,34 +42,69 @@ return function (uri, callback) return end - local mark - for _, group in ipairs(state.ast.docs.groups) do - for _, doc in ipairs(group) do - if doc.type == 'doc.class' then - mark = {} - elseif doc.type == 'doc.field' then - if mark then - await.delay() - local name - if doc.field.type == 'doc.type' then - name = ('[%s]'):format(vm.getInfer(doc.field):view(uri)) - else - name = ('%q'):format(doc.field[1]) - end - local eventName = getFieldEventName(doc) - if eventName then - name = name .. '|' .. eventName - end - if mark[name] then - callback { - start = doc.field.start, - finish = doc.field.finish, - message = lang.script('DIAG_DUPLICATE_DOC_FIELD', name), - } - end - mark[name] = true + local cachedKeys = {} + + ---@param field parser.object + ---@return string? + local function viewKey(field) + if not cachedKeys[field] then + local view = vm.viewKey(field, uri) + if view then + local eventName = getFieldEventName(field) + if eventName then + view = view .. '|' .. eventName + end + end + cachedKeys[field] = view or false + end + return cachedKeys[field] or nil + end + + ---@async + ---@param myField parser.object + local function checkField(myField) + await.delay() + local myView = viewKey(myField) + if not myView then + return + end + + local class = myField.class + if not class then + return + end + for _, set in ipairs(vm.getGlobal('type', class.class[1]):getSets(uri)) do + if not set.fields then + goto CONTINUE + end + for _, field in ipairs(set.fields) do + if field == myField then + goto CONTINUE + end + local view = viewKey(field) + if view ~= myView then + goto CONTINUE end + callback { + start = myField.field.start, + finish = myField.field.finish, + message = lang.script('DIAG_DUPLICATE_DOC_FIELD', myView), + related = {{ + start = field.field.start, + finish = field.field.finish, + uri = guide.getUri(field), + }} + } + do return end + ::CONTINUE:: end + ::CONTINUE:: + end + end + + for _, doc in ipairs(state.ast.docs) do + if doc.type == 'doc.field' then + checkField(doc) end end end diff --git a/script/core/diagnostics/duplicate-set-field.lua b/script/core/diagnostics/duplicate-set-field.lua index ce67ab462..a4b205dda 100644 --- a/script/core/diagnostics/duplicate-set-field.lua +++ b/script/core/diagnostics/duplicate-set-field.lua @@ -5,96 +5,80 @@ local guide = require 'parser.guide' local vm = require 'vm' local await = require 'await' +local sourceTypes = { + 'setfield', + 'setmethod', + 'setindex', +} + +---@param source parser.object +---@return parser.object? +local function getTopFunctionOfIf(source) + while true do + if source.type == 'ifblock' + or source.type == 'elseifblock' + or source.type == 'elseblock' + or source.type == 'function' + or source.type == 'main' then + return source + end + source = source.parent + end + return nil +end + ---@async return function (uri, callback) - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then + return + end + + if vm.isMetaFile(uri) then return end ---@async - guide.eachSourceType(ast.ast, 'local', function (source) - if not source.ref then + guide.eachSourceTypes(state.ast, sourceTypes, function (src) + await.delay() + local name = guide.getKeyName(src) + if not name then return end - await.delay() - local sets = {} - for _, ref in ipairs(source.ref) do - if ref.type ~= 'getlocal' then + local value = vm.getObjectValue(src) + if not value or value.type ~= 'function' then + return + end + local myTopBlock = getTopFunctionOfIf(src) + local defs = vm.getDefs(src) + for _, def in ipairs(defs) do + if def == src then goto CONTINUE end - local nxt = ref.next - if not nxt then + if def.type ~= 'setfield' + and def.type ~= 'setmethod' + and def.type ~= 'setindex' then goto CONTINUE end - if nxt.type == 'setfield' - or nxt.type == 'setmethod' - or nxt.type == 'setindex' then - local name = guide.getKeyName(nxt) - if not name then - goto CONTINUE - end - local value = vm.getObjectValue(nxt) - if not value or value.type ~= 'function' then - goto CONTINUE - end - if not sets[name] then - sets[name] = {} - end - sets[name][#sets[name]+1] = nxt - end - ::CONTINUE:: - end - for name, values in pairs(sets) do - if #values <= 1 then - goto CONTINUE_SETS - end - local blocks = {} - for _, value in ipairs(values) do - local block = guide.getBlock(value) - if block then - if not blocks[block] then - blocks[block] = {} - end - blocks[block][#blocks[block]+1] = value - end + local defTopBlock = getTopFunctionOfIf(def) + if uri == guide.getUri(def) and myTopBlock ~= defTopBlock then + goto CONTINUE end - for _, defs in pairs(blocks) do - if #defs <= 1 then - goto CONTINUE_BLOCKS - end - local related = {} - for i = 1, #defs do - local def = defs[i] - related[i] = { - start = def.start, - finish = def.finish, - uri = uri, - } - end - for i = 1, #defs - 1 do - local def = defs[i] - callback { - start = def.start, - finish = def.finish, - related = related, - message = lang.script('DIAG_DUPLICATE_SET_FIELD', name), - level = define.DiagnosticSeverity.Hint, - tags = { define.DiagnosticTag.Unnecessary }, - } - end - for i = #defs, #defs do - local def = defs[i] - callback { - start = def.start, - finish = def.finish, - related = related, - message = lang.script('DIAG_DUPLICATE_SET_FIELD', name), - } - end - ::CONTINUE_BLOCKS:: + local defValue = vm.getObjectValue(def) + if not defValue or defValue.type ~= 'function' then + goto CONTINUE end - ::CONTINUE_SETS:: + callback { + start = src.start, + finish = src.finish, + related = {{ + start = def.start, + finish = def.finish, + uri = guide.getUri(def), + }}, + message = lang.script('DIAG_DUPLICATE_SET_FIELD', name), + } + ::CONTINUE:: end end) end diff --git a/script/core/diagnostics/global-element.lua b/script/core/diagnostics/global-element.lua new file mode 100644 index 000000000..e9dd46ce9 --- /dev/null +++ b/script/core/diagnostics/global-element.lua @@ -0,0 +1,57 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local config = require 'config' +local vm = require 'vm' +local util = require 'utility' + +local function isDocClass(source) + if not source.bindDocs then + return false + end + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.class' then + return true + end + end + return false +end + +-- If global elements are discouraged by coding convention, this diagnostic helps with reminding about that +-- Exceptions may be added to Lua.diagnostics.globals +return function (uri, callback) + local ast = files.getState(uri) + if not ast then + return + end + + local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + + guide.eachSourceType(ast.ast, 'setglobal', function (source) + local name = guide.getKeyName(source) + if not name or definedGlobal[name] then + return + end + -- If the assignment is marked as doc.class, then it is considered allowed + if isDocClass(source) then + return + end + if definedGlobal[name] == nil then + definedGlobal[name] = false + local global = vm.getGlobal('variable', name) + if global then + for _, set in ipairs(global:getSets(uri)) do + if vm.isMetaFile(guide.getUri(set)) then + definedGlobal[name] = true + return + end + end + end + end + callback { + start = source.start, + finish = source.finish, + message = lang.script.DIAG_GLOBAL_ELEMENT, + } + end) +end diff --git a/script/core/diagnostics/global-in-nil-env.lua b/script/core/diagnostics/global-in-nil-env.lua index e154080c7..daf1f99d5 100644 --- a/script/core/diagnostics/global-in-nil-env.lua +++ b/script/core/diagnostics/global-in-nil-env.lua @@ -13,6 +13,9 @@ return function (uri, callback) if node.tag == '_ENV' then return end + if guide.isParam(node) then + return + end if not node.value or node.value.type == 'nil' then callback { diff --git a/script/core/diagnostics/helper/missing-doc-helper.lua b/script/core/diagnostics/helper/missing-doc-helper.lua new file mode 100644 index 000000000..116173f2d --- /dev/null +++ b/script/core/diagnostics/helper/missing-doc-helper.lua @@ -0,0 +1,83 @@ +local lang = require 'language' + +local m = {} + +local function findParam(docs, param) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.param' then + if doc.param[1] == param then + return true + end + end + end + + return false +end + +local function findReturn(docs, index) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' then + for _, ret in ipairs(doc.returns) do + if ret.returnIndex == index then + return true + end + end + end + end + + return false +end + +local function checkFunction(source, callback, commentId, paramId, returnId) + local functionName = source.parent[1] + local argCount = source.args and #source.args or 0 + + if argCount == 0 and not source.returns and not source.bindDocs then + callback { + start = source.start, + finish = source.finish, + message = lang.script(commentId, functionName), + } + end + + if argCount > 0 then + for _, arg in ipairs(source.args) do + local argName = arg[1] + if argName ~= 'self' + and argName ~= '_' then + if not findParam(source.bindDocs, argName) then + callback { + start = arg.start, + finish = arg.finish, + message = lang.script(paramId, argName, functionName), + } + end + end + end + end + + if source.returns then + for _, ret in ipairs(source.returns) do + for index, expr in ipairs(ret) do + if not findReturn(source.bindDocs, index) then + callback { + start = expr.start, + finish = expr.finish, + message = lang.script(returnId, index, functionName), + } + end + end + end + end +end + +m.CheckFunction = checkFunction +return m diff --git a/script/core/diagnostics/incomplete-signature-doc.lua b/script/core/diagnostics/incomplete-signature-doc.lua new file mode 100644 index 000000000..1ffbb77a2 --- /dev/null +++ b/script/core/diagnostics/incomplete-signature-doc.lua @@ -0,0 +1,109 @@ +-- incomplete-signature-doc +local files = require 'files' +local lang = require 'language' +local guide = require "parser.guide" +local await = require 'await' + +local function findParam(docs, param) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.param' then + if doc.param[1] == param then + return true + end + end + end + + return false +end + +local function findReturn(docs, index) + if not docs then + return false + end + + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' then + for _, ret in ipairs(doc.returns) do + if ret.returnIndex == index then + return true + end + end + end + end + + return false +end + +--- check if there's any signature doc (@param or @return), or just comments, @async, ... +local function findSignatureDoc(docs) + if not docs then + return false + end + for _, doc in ipairs(docs) do + if doc.type == 'doc.return' or doc.type == 'doc.param' then + return true + end + end + return false +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + if not state.ast then + return + end + + ---@async + guide.eachSourceType(state.ast, 'function', function (source) + await.delay() + + if not source.bindDocs then + return + end + + --- don't apply rule if there is no @param or @return annotation yet + --- so comments and @async can be applied without the need for a full documentation + if(not findSignatureDoc(source.bindDocs)) then + return + end + + if source.args and #source.args > 0 then + for _, arg in ipairs(source.args) do + local argName = arg[1] + if argName ~= 'self' + and argName ~= '_' then + if not findParam(source.bindDocs, argName) then + callback { + start = arg.start, + finish = arg.finish, + message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM', argName), + } + end + end + end + end + + if source.returns then + for _, ret in ipairs(source.returns) do + for index, expr in ipairs(ret) do + if not findReturn(source.bindDocs, index) then + callback { + start = expr.start, + finish = expr.finish, + message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN', index), + } + end + end + end + end + end) +end diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index c33de6cee..bb0489a81 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -6,29 +6,6 @@ local vm = require "vm.vm" local util = require 'utility' local diagd = require 'proto.diagnostic' --- ๆŠŠ่€—ๆ—ถๆœ€้•ฟ็š„่ฏŠๆ–ญๆ”พๅˆฐๆœ€ๅŽ้ข -local diagSort = { - ['redundant-value'] = 100, - ['not-yieldable'] = 100, - ['deprecated'] = 100, - ['undefined-field'] = 110, - ['redundant-parameter'] = 110, - ['cast-local-type'] = 120, - ['assign-type-mismatch'] = 120, - ['param-type-mismatch'] = 120, - ['missing-return'] = 120, - ['missing-return-value'] = 120, - ['redundant-return-value'] = 120, -} - -local diagList = {} -for k in pairs(define.DiagnosticDefaultSeverity) do - diagList[#diagList+1] = k -end -table.sort(diagList, function (a, b) - return (diagSort[a] or 0) < (diagSort[b] or 0) -end) - local sleepRest = 0.0 ---@async @@ -117,20 +94,21 @@ end ---@param name string ---@param isScopeDiag boolean ---@param response async fun(result: any) +---@return boolean local function check(uri, name, isScopeDiag, response) local disables = config.get(uri, 'Lua.diagnostics.disable') if util.arrayHas(disables, name) then - return + return false end local severity = getSeverity(uri, name) local status = getStatus(uri, name) if status == 'None' then - return + return false end if status == 'Opened' and not files.isOpen(uri) then - return + return false end local level = define.DiagnosticSeverity[severity] @@ -163,6 +141,25 @@ local function check(uri, name, isScopeDiag, response) if DIAGTIMES then DIAGTIMES[name] = (DIAGTIMES[name] or 0) + passed end + return true +end + +local diagList +local diagCosts = {} +local diagCount = {} +local function buildDiagList() + if not diagList then + diagList = {} + for name in pairs(define.DiagnosticDefaultSeverity) do + diagList[#diagList+1] = name + end + end + table.sort(diagList, function (a, b) + local time1 = (diagCosts[a] or 0) / (diagCount[a] or 1) + local time2 = (diagCosts[b] or 0) / (diagCount[b] or 1) + return time1 < time2 + end) + return diagList end ---@async @@ -176,9 +173,15 @@ return function (uri, isScopeDiag, response, checked) return nil end - for _, name in ipairs(diagList) do + for _, name in ipairs(buildDiagList()) do await.delay() - check(uri, name, isScopeDiag, response) + local clock = os.clock() + local suc = check(uri, name, isScopeDiag, response) + if suc then + local cost = os.clock() - clock + diagCosts[name] = (diagCosts[name] or 0) + cost + diagCount[name] = (diagCount[name] or 0) + 1 + end if checked then checked(name) end diff --git a/script/core/diagnostics/inject-field.lua b/script/core/diagnostics/inject-field.lua new file mode 100644 index 000000000..2866eef8f --- /dev/null +++ b/script/core/diagnostics/inject-field.lua @@ -0,0 +1,147 @@ +local files = require 'files' +local vm = require 'vm' +local lang = require 'language' +local guide = require 'parser.guide' +local await = require 'await' +local hname = require 'core.hover.name' + +local skipCheckClass = { + ['unknown'] = true, + ['any'] = true, + ['table'] = true, +} + +---@async +return function (uri, callback) + local ast = files.getState(uri) + if not ast then + return + end + + ---@async + local function checkInjectField(src) + await.delay() + + local node = src.node + if not node then + return + end + local ok + for view in vm.getInfer(node):eachView(uri) do + if skipCheckClass[view] then + return + end + ok = true + end + if not ok then + return + end + + local isExact + local class = vm.getDefinedClass(uri, node) + if class then + for _, doc in ipairs(class:getSets(uri)) do + if vm.docHasAttr(doc, 'exact') then + isExact = true + break + end + end + if not isExact then + return + end + if src.type == 'setmethod' + and not guide.getSelfNode(node) then + return + end + end + + for _, def in ipairs(vm.getDefs(src)) do + local dnode = def.node + if dnode + and not isExact + and vm.getDefinedClass(uri, dnode) then + return + end + if def.type == 'doc.type.field' then + return + end + if def.type == 'doc.field' then + return + end + end + + local howToFix = '' + if not isExact then + howToFix = lang.script('DIAG_INJECT_FIELD_FIX_CLASS', { + node = hname(node), + fix = '---@class', + }) + for _, ndef in ipairs(vm.getDefs(node)) do + if ndef.type == 'doc.type.table' then + howToFix = lang.script('DIAG_INJECT_FIELD_FIX_TABLE', { + fix = '[any]: any', + }) + break + end + end + end + + local message = lang.script('DIAG_INJECT_FIELD', { + class = vm.getInfer(node):view(uri), + field = guide.getKeyName(src), + fix = howToFix, + }) + if src.type == 'setfield' and src.field then + callback { + start = src.field.start, + finish = src.field.finish, + message = message, + } + elseif src.type == 'setmethod' and src.method then + callback { + start = src.method.start, + finish = src.method.finish, + message = message, + } + end + end + guide.eachSourceType(ast.ast, 'setfield', checkInjectField) + guide.eachSourceType(ast.ast, 'setmethod', checkInjectField) + + ---@async + local function checkExtraTableField(src) + await.delay() + + if not src.bindSource then + return + end + if not vm.docHasAttr(src, 'exact') then + return + end + local value = src.bindSource.value + if not value or value.type ~= 'table' then + return + end + for _, field in ipairs(value) do + local defs = vm.getDefs(field) + for _, def in ipairs(defs) do + if def.type == 'doc.field' then + goto nextField + end + end + local message = lang.script('DIAG_INJECT_FIELD', { + class = vm.getInfer(src):view(uri), + field = guide.getKeyName(src), + fix = '', + }) + callback { + start = field.start, + finish = field.finish, + message = message, + } + ::nextField:: + end + end + + guide.eachSourceType(ast.ast, 'doc.class', checkExtraTableField) +end diff --git a/script/core/diagnostics/invisible.lua b/script/core/diagnostics/invisible.lua new file mode 100644 index 000000000..7172c4a5b --- /dev/null +++ b/script/core/diagnostics/invisible.lua @@ -0,0 +1,67 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local vm = require 'vm.vm' +local await = require 'await' + +local checkTypes = {'getfield', 'setfield', 'getmethod', 'setmethod', 'getindex', 'setindex'} + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + ---@async + guide.eachSourceTypes(state.ast, checkTypes, function (src) + local child = src.field or src.method or src.index + if not child then + return + end + local key = guide.getKeyName(src) + if not key then + return + end + await.delay() + local defs = vm.getDefs(src) + for _, def in ipairs(defs) do + if not vm.isVisible(src.node, def) then + if vm.getVisibleType(def) == 'private' then + callback { + start = child.start, + finish = child.finish, + uri = uri, + message = lang.script('DIAG_INVISIBLE_PRIVATE', { + field = key, + class = vm.getParentClass(def):getName(), + }), + } + elseif vm.getVisibleType(def) == 'protected' then + callback { + start = child.start, + finish = child.finish, + uri = uri, + message = lang.script('DIAG_INVISIBLE_PROTECTED', { + field = key, + class = vm.getParentClass(def):getName(), + }), + } + elseif vm.getVisibleType(def) == 'package' then + callback { + start = child.start, + finish = child.finish, + uri = uri, + message = lang.script('DIAG_INVISIBLE_PACKAGE', { + field = key, + uri = guide.getUri(def), + }), + } + else + error('Unknown visible type: ' .. vm.getVisibleType(def)) + end + break + end + end + end) +end diff --git a/script/core/diagnostics/missing-fields.lua b/script/core/diagnostics/missing-fields.lua new file mode 100644 index 000000000..210920fd9 --- /dev/null +++ b/script/core/diagnostics/missing-fields.lua @@ -0,0 +1,84 @@ +local vm = require 'vm' +local files = require 'files' +local guide = require 'parser.guide' +local await = require 'await' +local lang = require 'language' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + ---@async + guide.eachSourceType(state.ast, 'table', function (src) + await.delay() + + local defs = vm.getDefs(src) + for _, def in ipairs(defs) do + if def.type == 'doc.class' and def.bindSource then + if guide.isInRange(def.bindSource, src.start) then + return + end + end + if def.type == 'doc.type.array' + or def.type == 'doc.type.table' then + return + end + end + local warnings = {} + for _, def in ipairs(defs) do + if def.type == 'doc.class' then + if not def.fields then + return + end + + local requiresKeys = {} + for _, field in ipairs(def.fields) do + if not field.optional + and not vm.compileNode(field):isNullable() then + local key = vm.getKeyName(field) + if key and not requiresKeys[key] then + requiresKeys[key] = true + requiresKeys[#requiresKeys+1] = key + end + end + end + + if #requiresKeys == 0 then + return + end + local myKeys = {} + for _, field in ipairs(src) do + local key = vm.getKeyName(field) + if key then + myKeys[key] = true + end + end + + local missedKeys = {} + for _, key in ipairs(requiresKeys) do + if not myKeys[key] then + missedKeys[#missedKeys+1] = ('`%s`'):format(key) + end + end + + if #missedKeys == 0 then + return + end + + warnings[#warnings+1] = lang.script('DIAG_MISSING_FIELDS', def.class[1], table.concat(missedKeys, ', ')) + end + end + + if #warnings == 0 then + return + end + callback { + start = src.start, + finish = src.finish, + message = table.concat(warnings, '\n') + } + end) +end diff --git a/script/core/diagnostics/missing-global-doc.lua b/script/core/diagnostics/missing-global-doc.lua new file mode 100644 index 000000000..e104d232c --- /dev/null +++ b/script/core/diagnostics/missing-global-doc.lua @@ -0,0 +1,27 @@ +local files = require 'files' +local guide = require "parser.guide" +local await = require 'await' +local helper = require 'core.diagnostics.helper.missing-doc-helper' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + if not state.ast then + return + end + + ---@async + guide.eachSourceType(state.ast, 'function', function (source) + await.delay() + + if source.parent.type ~= 'setglobal' then + return + end + + helper.CheckFunction(source, callback, 'DIAG_MISSING_GLOBAL_DOC_COMMENT', 'DIAG_MISSING_GLOBAL_DOC_PARAM', 'DIAG_MISSING_GLOBAL_DOC_RETURN') + end) +end diff --git a/script/core/diagnostics/missing-local-export-doc.lua b/script/core/diagnostics/missing-local-export-doc.lua new file mode 100644 index 000000000..da413961e --- /dev/null +++ b/script/core/diagnostics/missing-local-export-doc.lua @@ -0,0 +1,52 @@ +local files = require 'files' +local guide = require "parser.guide" +local await = require 'await' +local helper = require 'core.diagnostics.helper.missing-doc-helper' + +---@async +local function findSetField(ast, name, callback) + ---@async + guide.eachSourceType(ast, 'setfield', function (source) + await.delay() + if source.node[1] == name then + local funcPtr = source.value.node + if not funcPtr then + return + end + local func = funcPtr.value + if not func then + return + end + if funcPtr.type == 'local' and func.type == 'function' then + helper.CheckFunction(func, callback, 'DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT', 'DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM', 'DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN') + end + end + end) +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + + if not state then + return + end + + if not state.ast then + return + end + + ---@async + guide.eachSourceType(state.ast, 'return', function (source) + await.delay() + --table + + for i, ret in ipairs(source) do + if ret.type == 'getlocal' then + if ret.node.value and ret.node.value.type == 'table' then + findSetField(state.ast, ret[1], callback) + end + end + end + end) +end diff --git a/script/core/diagnostics/missing-return-value.lua b/script/core/diagnostics/missing-return-value.lua index 2156d66c7..5c672b546 100644 --- a/script/core/diagnostics/missing-return-value.lua +++ b/script/core/diagnostics/missing-return-value.lua @@ -4,18 +4,6 @@ local vm = require 'vm' local lang = require 'language' local await = require 'await' -local function hasDocReturn(func) - if not func.bindDocs then - return false - end - for _, doc in ipairs(func.bindDocs) do - if doc.type == 'doc.return' then - return true - end - end - return false -end - ---@async return function (uri, callback) local state = files.getState(uri) @@ -26,17 +14,14 @@ return function (uri, callback) ---@async guide.eachSourceType(state.ast, 'function', function (source) await.delay() - if not hasDocReturn(source) then + local returns = source.returns + if not returns then return end - local min = vm.countReturnsOfFunction(source) + local min = vm.countReturnsOfSource(source) if min == 0 then return end - local returns = source.returns - if not returns then - return - end for _, ret in ipairs(returns) do local rmin, rmax = vm.countList(ret) if rmax < min then diff --git a/script/core/diagnostics/missing-return.lua b/script/core/diagnostics/missing-return.lua index e3539ac06..2b5c90d23 100644 --- a/script/core/diagnostics/missing-return.lua +++ b/script/core/diagnostics/missing-return.lua @@ -7,7 +7,7 @@ local await = require 'await' ---@param block parser.object ---@return boolean local function hasReturn(block) - if block.hasReturn or block.hasError then + if block.hasReturn or block.hasExit then return true end if block.type == 'if' then @@ -38,17 +38,6 @@ local function hasReturn(block) return false end ----@param func parser.object ----@return boolean -local function isEmptyFunction(func) - if #func > 0 then - return false - end - local startRow = guide.rowColOf(func.start) - local finishRow = guide.rowColOf(func.finish) - return finishRow - startRow <= 1 -end - ---@async return function (uri, callback) local state = files.getState(uri) @@ -56,14 +45,16 @@ return function (uri, callback) return end + local isMeta = vm.isMetaFile(uri) + ---@async guide.eachSourceType(state.ast, 'function', function (source) -- check declare only - if isEmptyFunction(source) then + if isMeta and vm.isEmptyFunction(source) then return end await.delay() - if vm.countReturnsOfFunction(source, true) == 0 then + if vm.countReturnsOfSource(source) == 0 then return end if hasReturn(source) then @@ -74,8 +65,7 @@ return function (uri, callback) if lastAction then pos = lastAction.range or lastAction.finish else - local row = guide.rowColOf(source.finish) - pos = guide.positionOf(row - 1, 0) + pos = source.keyword[3] or source.finish end callback { start = pos, diff --git a/script/core/diagnostics/name-style-check.lua b/script/core/diagnostics/name-style-check.lua new file mode 100644 index 000000000..4bdb068f2 --- /dev/null +++ b/script/core/diagnostics/name-style-check.lua @@ -0,0 +1,35 @@ +local files = require 'files' +local converter = require 'proto.converter' +local log = require 'log' +local nameStyle = require 'provider.name-style' + + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + local text = state.originText + + local status, diagnosticInfos = nameStyle.nameStyleCheck(uri, text) + + if not status then + if diagnosticInfos ~= nil then + log.error(diagnosticInfos) + end + + return + end + + if diagnosticInfos then + for _, diagnosticInfo in ipairs(diagnosticInfos) do + callback { + start = converter.unpackPosition(state, diagnosticInfo.range.start), + finish = converter.unpackPosition(state, diagnosticInfo.range["end"]), + message = diagnosticInfo.message, + data = diagnosticInfo.data + } + end + end +end diff --git a/script/core/diagnostics/param-type-mismatch.lua b/script/core/diagnostics/param-type-mismatch.lua index 6f34f5790..acbf9c8c2 100644 --- a/script/core/diagnostics/param-type-mismatch.lua +++ b/script/core/diagnostics/param-type-mismatch.lua @@ -4,34 +4,81 @@ local guide = require 'parser.guide' local vm = require 'vm' local await = require 'await' ----@async -return function (uri, callback) - local state = files.getState(uri) - if not state then - return +---@param defNode vm.node +local function expandGenerics(defNode) + ---@type parser.object[] + local generics = {} + for dn in defNode:eachObject() do + if dn.type == 'doc.generic.name' then + ---@cast dn parser.object + generics[#generics+1] = dn + end + end + + for _, generic in ipairs(generics) do + defNode:removeObject(generic) end - ---@param funcNode vm.node - ---@param i integer - ---@return vm.node? - local function getDefNode(funcNode, i) - local defNode = vm.createNode() - for f in funcNode:eachObject() do - if f.type == 'function' - or f.type == 'doc.type.function' then - local param = f.args and f.args[i] - if param then - defNode:merge(vm.compileNode(param)) - if param[1] == '...' then - defNode:addOptional() - end + for _, generic in ipairs(generics) do + local limits = generic.generic and generic.generic.extends + if limits then + defNode:merge(vm.compileNode(limits)) + else + local unknownType = vm.declareGlobal('type', 'unknown') + defNode:merge(unknownType) + end + end +end + +---@param funcNode vm.node +---@param i integer +---@param uri uri +---@return vm.node? +local function getDefNode(funcNode, i, uri) + local defNode = vm.createNode() + for src in funcNode:eachObject() do + if src.type == 'function' + or src.type == 'doc.type.function' then + local param = src.args and src.args[i] + if param then + defNode:merge(vm.compileNode(param)) + if param[1] == '...' then + defNode:addOptional() end end end - if defNode:isEmpty() then - return nil + end + if defNode:isEmpty() then + return nil + end + + expandGenerics(defNode) + + return defNode +end + +---@param funcNode vm.node +---@param i integer +---@return vm.node +local function getRawDefNode(funcNode, i) + local defNode = vm.createNode() + for f in funcNode:eachObject() do + if f.type == 'function' + or f.type == 'doc.type.function' then + local param = f.args and f.args[i] + if param then + defNode:merge(vm.compileNode(param)) + end end - return defNode + end + return defNode +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return end ---@async @@ -42,28 +89,32 @@ return function (uri, callback) await.delay() local funcNode = vm.compileNode(source.node) for i, arg in ipairs(source.args) do - if i == 1 and source.node.type == 'getmethod' then + local refNode = vm.compileNode(arg) + if not refNode then goto CONTINUE end - local refNode = vm.compileNode(arg) - local defNode = getDefNode(funcNode, i) + local defNode = getDefNode(funcNode, i, uri) if not defNode then goto CONTINUE end if arg.type == 'getfield' - or arg.type == 'getindex' then + or arg.type == 'getindex' + or arg.type == 'self' then -- ็”ฑไบŽๆ— ๆณ•ๅฏนๅญ—ๆฎต่ฟ›่กŒ็ฑปๅž‹ๆ”ถ็ช„๏ผŒ -- ๅ› ๆญคๅฐ†ๅ‡ๅ€ผ็งป้™คๅ†่ฟ›่กŒๆฃ€ๆŸฅ refNode = refNode:copy():setTruthy() end - if not vm.canCastType(uri, defNode, refNode) then + local errs = {} + if not vm.canCastType(uri, defNode, refNode, errs) then + local rawDefNode = getRawDefNode(funcNode, i) + assert(errs) callback { start = arg.start, finish = arg.finish, message = lang.script('DIAG_PARAM_TYPE_MISMATCH', { - def = vm.getInfer(defNode):view(uri), + def = vm.getInfer(rawDefNode):view(uri), ref = vm.getInfer(refNode):view(uri), - }) + }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs), } end ::CONTINUE:: diff --git a/script/core/diagnostics/redundant-parameter.lua b/script/core/diagnostics/redundant-parameter.lua index 9898d9bd1..667f9c61f 100644 --- a/script/core/diagnostics/redundant-parameter.lua +++ b/script/core/diagnostics/redundant-parameter.lua @@ -52,4 +52,22 @@ return function (uri, callback) end end end) + + ---@async + guide.eachSourceType(state.ast, 'function', function (source) + await.delay() + if not source.args then + return + end + local _, funcArgs = vm.countParamsOfSource(source) + local myArgs = #source.args + for i = funcArgs + 1, myArgs do + local arg = source.args[i] + callback { + start = arg.start, + finish = arg.finish, + message = lang.script('DIAG_OVER_MAX_ARGS', funcArgs, myArgs), + } + end + end) end diff --git a/script/core/diagnostics/redundant-return-value.lua b/script/core/diagnostics/redundant-return-value.lua index 36432f986..186678405 100644 --- a/script/core/diagnostics/redundant-return-value.lua +++ b/script/core/diagnostics/redundant-return-value.lua @@ -4,18 +4,6 @@ local vm = require 'vm' local lang = require 'language' local await = require 'await' -local function hasDocReturn(func) - if not func.bindDocs then - return false - end - for _, doc in ipairs(func.bindDocs) do - if doc.type == 'doc.return' then - return true - end - end - return false -end - ---@async return function (uri, callback) local state = files.getState(uri) @@ -25,15 +13,12 @@ return function (uri, callback) ---@async guide.eachSourceType(state.ast, 'function', function (source) - await.delay() - if not hasDocReturn(source) then - return - end - local _, max = vm.countReturnsOfFunction(source) local returns = source.returns if not returns then return end + await.delay() + local _, max = vm.countReturnsOfSource(source) for _, ret in ipairs(returns) do local rmin, rmax = vm.countList(ret) if rmin > max then diff --git a/script/core/diagnostics/return-type-mismatch.lua b/script/core/diagnostics/return-type-mismatch.lua index cce4aad8b..9643fb004 100644 --- a/script/core/diagnostics/return-type-mismatch.lua +++ b/script/core/diagnostics/return-type-mismatch.lua @@ -3,21 +3,38 @@ local lang = require 'language' local guide = require 'parser.guide' local vm = require 'vm' local await = require 'await' +local util = require 'utility' ---@param func parser.object ---@return vm.node[]? local function getDocReturns(func) - if not func.bindDocs then - return nil + ---@type table + local returns = util.defaultTable(function () + return vm.createNode() + end) + if func.bindDocs then + for _, doc in ipairs(func.bindDocs) do + if doc.type == 'doc.return' then + for _, ret in ipairs(doc.returns) do + returns[ret.returnIndex]:merge(vm.compileNode(ret)) + end + end + if doc.type == 'doc.overload' then + for i, ret in ipairs(doc.overload.returns) do + returns[i]:merge(vm.compileNode(ret)) + end + end + end end - local returns = {} - for _, doc in ipairs(func.bindDocs) do - if doc.type == 'doc.return' then - for _, ret in ipairs(doc.returns) do - returns[ret.returnIndex] = vm.compileNode(ret) + for nd in vm.compileNode(func):eachObject() do + if nd.type == 'doc.type.function' then + ---@cast nd parser.object + for i, ret in ipairs(nd.returns) do + returns[i]:merge(vm.compileNode(ret)) end end end + setmetatable(returns, nil) if #returns == 0 then return nil end @@ -44,7 +61,8 @@ return function (uri, callback) retNode = retNode:copy():removeOptional() end end - if not vm.canCastType(uri, docRet, retNode) then + local errs = {} + if not vm.canCastType(uri, docRet, retNode, errs) then callback { start = exp.start, finish = exp.finish, @@ -52,7 +70,7 @@ return function (uri, callback) def = vm.getInfer(docRet):view(uri), ref = vm.getInfer(retNode):view(uri), index = i, - }), + }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs), } end end diff --git a/script/core/diagnostics/spell-check.lua b/script/core/diagnostics/spell-check.lua index 7369a2352..a4cb87be3 100644 --- a/script/core/diagnostics/spell-check.lua +++ b/script/core/diagnostics/spell-check.lua @@ -6,10 +6,11 @@ local spell = require 'provider.spell' ---@async return function(uri, callback) - local text = files.getOriginText(uri) - if not text then + local state = files.getState(uri) + if not state then return end + local text = state.originText local status, diagnosticInfos = spell.spellCheck(uri, text) @@ -24,8 +25,8 @@ return function(uri, callback) if diagnosticInfos then for _, diagnosticInfo in ipairs(diagnosticInfos) do callback { - start = converter.unpackPosition(uri, diagnosticInfo.range.start), - finish = converter.unpackPosition(uri, diagnosticInfo.range["end"]), + start = converter.unpackPosition(state, diagnosticInfo.range.start), + finish = converter.unpackPosition(state, diagnosticInfo.range["end"]), message = diagnosticInfo.message, data = diagnosticInfo.data } diff --git a/script/core/diagnostics/undefined-doc-name.lua b/script/core/diagnostics/undefined-doc-name.lua index bacd42886..1c55f3bf3 100644 --- a/script/core/diagnostics/undefined-doc-name.lua +++ b/script/core/diagnostics/undefined-doc-name.lua @@ -13,16 +13,6 @@ return function (uri, callback) return end - local function hasNameOfGeneric(name, source) - if not source.typeGeneric then - return false - end - if not source.typeGeneric[name] then - return false - end - return true - end - guide.eachSource(state.ast.docs, function (source) if source.type ~= 'doc.extends.name' and source.type ~= 'doc.type.name' then @@ -32,11 +22,10 @@ return function (uri, callback) return end local name = source[1] - if name == '...' or name == '_' then + if name == '...' or name == '_' or name == 'self' then return end - if #vm.getDocSets(uri, name) > 0 - or hasNameOfGeneric(name, source) then + if #vm.getDocSets(uri, name) > 0 then return end callback { diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index a83241f5a..4fd55966d 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -8,13 +8,6 @@ local skipCheckClass = { ['unknown'] = true, ['any'] = true, ['table'] = true, - ['nil'] = true, - ['number'] = true, - ['integer'] = true, - ['boolean'] = true, - ['function'] = true, - ['userdata'] = true, - ['lightuserdata'] = true, } ---@async @@ -61,5 +54,4 @@ return function (uri, callback) end guide.eachSourceType(ast.ast, 'getfield', checkUndefinedField) guide.eachSourceType(ast.ast, 'getmethod', checkUndefinedField) - guide.eachSourceType(ast.ast, 'getindex', checkUndefinedField) end diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index 179c92043..d9d949598 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -20,41 +20,21 @@ return function (uri, callback) return end - local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) - local rspecial = config.get(uri, 'Lua.runtime.special') - local cache = {} - -- ้ๅކๅ…จๅฑ€ๅ˜้‡๏ผŒๆฃ€ๆŸฅๆ‰€ๆœ‰ๆฒกๆœ‰ set ๆจกๅผ็š„ๅ…จๅฑ€ๅ˜้‡ guide.eachSourceType(state.ast, 'getglobal', function (src) ---@async - local key = src[1] - if not key then - return - end - if dglobals[key] then - return - end - if rspecial[key] then - return - end - local node = src.node - if node.tag ~= '_ENV' then - return - end - if cache[key] == nil then - await.delay() - cache[key] = vm.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)) + if vm.isUndefinedGlobal(src) then + local key = src[1] + 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, + undefinedGlobal = src[1] + } end - callback { - start = src.start, - finish = src.finish, - message = message, - } end) end diff --git a/script/core/diagnostics/unknown-cast-variable.lua b/script/core/diagnostics/unknown-cast-variable.lua index 3f082a50e..7c12e4d30 100644 --- a/script/core/diagnostics/unknown-cast-variable.lua +++ b/script/core/diagnostics/unknown-cast-variable.lua @@ -16,15 +16,15 @@ return function (uri, callback) end for _, doc in ipairs(state.ast.docs) do - if doc.type == 'doc.cast' and doc.loc then + if doc.type == 'doc.cast' and doc.name then await.delay() - local defs = vm.getDefs(doc.loc) + local defs = vm.getDefs(doc.name) local loc = defs[1] if not loc then callback { - start = doc.loc.start, - finish = doc.loc.finish, - message = lang.script('DIAG_UNKNOWN_CAST_VARIABLE', doc.loc[1]) + start = doc.name.start, + finish = doc.name.finish, + message = lang.script('DIAG_UNKNOWN_CAST_VARIABLE', doc.name[1]) } end end diff --git a/script/core/diagnostics/unreachable-code.lua b/script/core/diagnostics/unreachable-code.lua index 4f0a38b72..cbffe4db4 100644 --- a/script/core/diagnostics/unreachable-code.lua +++ b/script/core/diagnostics/unreachable-code.lua @@ -23,7 +23,7 @@ end ---@param block parser.object ---@return boolean local function hasReturn(block) - if block.hasReturn or block.hasError then + if block.hasReturn or block.hasExit then return true end if block.type == 'if' then diff --git a/script/core/diagnostics/unused-local.lua b/script/core/diagnostics/unused-local.lua index 8f2ee2173..52622eb24 100644 --- a/script/core/diagnostics/unused-local.lua +++ b/script/core/diagnostics/unused-local.lua @@ -65,24 +65,13 @@ local function isDocClass(source) return false end ----@param func parser.object ----@return boolean -local function isEmptyFunction(func) - if #func > 0 then - return false - end - local startRow = guide.rowColOf(func.start) - local finishRow = guide.rowColOf(func.finish) - return finishRow - startRow <= 1 -end - ---@param source parser.object local function isDeclareFunctionParam(source) if source.parent.type ~= 'funcargs' then return false end local func = source.parent.parent - return isEmptyFunction(func) + return vm.isEmptyFunction(func) end return function (uri, callback) @@ -90,6 +79,8 @@ return function (uri, callback) if not ast then return end + + local isMeta = vm.isMetaFile(uri) local ignorePatterns = config.get(uri, 'Lua.diagnostics.unusedLocalExclude') local ignore = glob.glob(ignorePatterns) guide.eachSourceType(ast.ast, 'local', function (source) @@ -107,7 +98,7 @@ return function (uri, callback) if isDocClass(source) then return end - if isDeclareFunctionParam(source) then + if isMeta and isDeclareFunctionParam(source) then return end local data = hasGet(source) diff --git a/script/core/document-symbol.lua b/script/core/document-symbol.lua index 6629ccbc9..8679deab9 100644 --- a/script/core/document-symbol.lua +++ b/script/core/document-symbol.lua @@ -3,28 +3,41 @@ local files = require 'files' local guide = require 'parser.guide' local define = require 'proto.define' local util = require 'utility' +local subber = require 'core.substring' -local function buildName(source, text) - local uri = guide.getUri(source) - local state = files.getState(uri) - local startOffset = guide.positionToOffset(state, source.start) +---@param text string +---@return string +local function clipLastLine(text) + if text:find '[\r\n]' then + return '... ' .. util.trim(text:match '[^\r\n]*$') + else + return text + end +end + +local function buildName(source, sub) if source.type == 'setmethod' or source.type == 'getmethod' then if source.method then - local finishOffset = guide.positionToOffset(state, source.method.finish) - return text:sub(startOffset + 1, finishOffset) + return clipLastLine(sub(source.start + 1, source.method.finish)) end end if source.type == 'setfield' or source.type == 'tablefield' or source.type == 'getfield' then if source.field then - local finishOffset = guide.positionToOffset(state, source.field.finish) - return text:sub(startOffset + 1, finishOffset) + return clipLastLine(sub(source.start + 1, source.field.finish)) end end - local finishOffset = guide.positionToOffset(state, source.finish) - return text:sub(startOffset + 1, finishOffset) + if source.type == 'tableindex' then + if source.index then + return ('[%s]'):format(clipLastLine(sub(source.index.start + 1, source.index.finish))) + end + end + if source.type == 'tableexp' then + return ('[%d]'):format(source.tindex) + end + return clipLastLine(sub(source.start + 1, source.finish)) end local function buildFunctionParams(func) @@ -46,204 +59,298 @@ local function buildFunctionParams(func) return table.concat(params, ', ') end -local function buildFunction(source, text, symbols) - local name = buildName(source, text) - local func = source.value - if source.type == 'tablefield' - or source.type == 'setfield' then - source = source.field - if not source then - return +local function buildTable(tbl, sub) + local buf = {} + for i = 1, 5 do + local field = tbl[i] + if not field then + break + end + if field.type == 'tablefield' + and field.field then + buf[#buf+1] = ('%s'):format(field.field[1]) + elseif field.type == 'tableindex' + and field.index then + buf[#buf+1] = ('[%s]'):format(sub(field.index.start + 1, field.index.finish)) + elseif field.type == 'tableexp' then + buf[#buf+1] = ('[%s]'):format(field.tindex) end end - local range, kind - if func.start > source.finish then - -- a = function() - range = { source.start, func.finish } - else - -- function f() - range = { func.start, func.finish } - end - if source.type == 'setmethod' then - kind = define.SymbolKind.Method - else - kind = define.SymbolKind.Function + if #tbl > 5 then + buf[#buf+1] = ('...(+%d)'):format(#tbl - 5) end - symbols[#symbols+1] = { - name = name, - detail = ('function (%s)'):format(buildFunctionParams(func)), - kind = kind, - range = range, - selectionRange = { source.start, source.finish }, - valueRange = { func.start, func.finish }, - } + return table.concat(buf, ', ') end -local function buildTable(tbl) +local function buildArray(tbl, sub) local buf = {} - for i = 1, 3 do + for i = 1, 5 do local field = tbl[i] if not field then break end - if field.type == 'tablefield' - and field.field then - buf[#buf+1] = ('%s'):format(field.field[1]) - end + buf[#buf+1] = sub(field.start + 1, field.finish) + end + if #tbl > 5 then + buf[#buf+1] = ('...(+%d)'):format(#tbl - 5) end return table.concat(buf, ', ') end -local function buildValue(source, text, symbols) - local name = buildName(source, text) +local function buildValue(source, sub, used, symbols) + local name = buildName(source, sub) local range, sRange, valueRange, kind local details = {} - if source.type == 'local' then + if source.type == 'local' then if source.parent.type == 'funcargs' then - details[1] = 'param' - range = { source.start, source.finish } - sRange = { source.start, source.finish } - kind = define.SymbolKind.Constant + kind = define.SymbolKind.Constant else - details[1] = 'local' - range = { source.start, source.finish } - sRange = { source.start, source.finish } - kind = define.SymbolKind.Variable + kind = define.SymbolKind.Variable end + range = { source.start, source.finish } + sRange = { source.start, source.finish } elseif source.type == 'setlocal' then - details[1] = 'setlocal' range = { source.start, source.finish } sRange = { source.start, source.finish } - kind = define.SymbolKind.Variable elseif source.type == 'setglobal' then - details[1] = 'global' range = { source.start, source.finish } sRange = { source.start, source.finish } - kind = define.SymbolKind.Class elseif source.type == 'tablefield' then if not source.field then return end - details[1] = 'field' range = { source.field.start, source.field.finish } sRange = { source.field.start, source.field.finish } - kind = define.SymbolKind.Property + elseif source.type == 'tableindex' then + if not source.index then + return + end + range = { source.index.start, source.index.finish } + sRange = { source.index.start, source.index.finish } + elseif source.type == 'tableexp' then + range = { source.value.start, source.value.finish } + sRange = { source.value.start, source.value.finish } elseif source.type == 'setfield' then if not source.field then return end - details[1] = 'field' range = { source.field.start, source.field.finish } sRange = { source.field.start, source.field.finish } - kind = define.SymbolKind.Field + elseif source.type == 'setmethod' then + if not source.method then + return + end + range = { source.method.start, source.method.finish } + sRange = { source.start, source.finish } else return end if source.value then + used[source.value] = true local literal = source.value[1] if source.value.type == 'boolean' then - details[2] = ' boolean' + kind = define.SymbolKind.Boolean if literal ~= nil then - details[3] = ' = ' - details[4] = util.viewLiteral(source.value[1]) + details[#details+1] = util.viewLiteral(source.value[1]) end elseif source.value.type == 'string' then - details[2] = ' string' + kind = define.SymbolKind.String if literal ~= nil then - details[3] = ' = ' - details[4] = util.viewLiteral(source.value[1]) + details[#details+1] = util.viewLiteral(source.value[1]) end elseif source.value.type == 'number' or source.value.type == 'integer' then - details[2] = ' number' + kind = define.SymbolKind.Number if literal ~= nil then - details[3] = ' = ' - details[4] = util.viewLiteral(source.value[1]) + details[#details+1] = util.viewLiteral(source.value[1]) end elseif source.value.type == 'table' then - details[2] = ' {' - details[3] = buildTable(source.value) - details[4] = '}' + kind = define.SymbolKind.Object + local lastField = source.value[#source.value] + if #source.value > 0 then + if lastField.type == 'tableexp' + and lastField.tindex == #source.value then + -- Array + kind = define.SymbolKind.Array + details[#details+1] = '[' + details[#details+1] = buildArray(source.value, sub) + details[#details+1] = ']' + else + -- Object + details[#details+1] = '{' + details[#details+1] = buildTable(source.value, sub) + details[#details+1] = '}' + end + end valueRange = { source.value.start, source.value.finish } elseif source.value.type == 'select' then if source.value.vararg and source.value.vararg.type == 'call' then valueRange = { source.value.start, source.value.finish } end + elseif source.value.type == 'function' then + details[#details+1] = ('function (%s)'):format(buildFunctionParams(source.value)) + if source.type == 'setmethod' then + kind = define.SymbolKind.Method + else + kind = define.SymbolKind.Function + end + valueRange = { source.value.start, source.value.finish } + range[1] = math.min(source.value.start, source.start) end range = { range[1], source.value.finish } end symbols[#symbols+1] = { name = name, detail = table.concat(details), - kind = kind, + kind = kind or define.SymbolKind.Variable, range = range, selectionRange = sRange, valueRange = valueRange, } end -local function buildSet(source, text, used, symbols) - local value = source.value - if value and value.type == 'function' then - used[value] = true - buildFunction(source, text, symbols) - else - buildValue(source, text, symbols) - end -end - -local function buildAnonymousFunction(source, text, used, symbols) +local function buildAnonymous(source, sub, used, symbols) if used[source] then return end used[source] = true local head = '' + local detail = '' local parent = source.parent if parent.type == 'return' then - head = 'return ' + head = 'return' elseif parent.type == 'callargs' then local call = parent.parent local node = call.node - head = buildName(node, text) .. ' -> ' + head = buildName(node, sub) + detail = '-> ' + end + if source.type == 'function' then + symbols[#symbols+1] = { + name = head, + detail = detail .. ('function (%s)'):format(buildFunctionParams(source)), + kind = define.SymbolKind.Function, + range = { source.start, source.finish }, + selectionRange = { source.keyword[1], source.keyword[2] }, + valueRange = { source.start, source.finish }, + } + elseif source.type == 'table' then + local kind = define.SymbolKind.Object + local details = {} + local lastField = source[#source] + if lastField then + if lastField.type == 'tableexp' + and lastField.tindex == #source then + -- Array + kind = define.SymbolKind.Array + details[#details+1] = '[' + details[#details+1] = buildArray(source, sub) + details[#details+1] = ']' + else + -- Object + details[#details+1] = '{' + details[#details+1] = buildTable(source, sub) + details[#details+1] = '}' + end + end + symbols[#symbols+1] = { + name = head, + detail = detail .. table.concat(details), + kind = kind, + range = { source.start, source.finish }, + selectionRange = { source.start, source.finish }, + valueRange = { source.start, source.finish }, + } end - symbols[#symbols+1] = { - name = '', - detail = ('%sfunction (%s)'):format(head, buildFunctionParams(source)), - kind = define.SymbolKind.Function, - range = { source.start, source.finish }, - selectionRange = { source.keyword[1], source.keyword[2] }, - valueRange = { source.start, source.finish }, - } end ----@async -local function buildSource(source, text, used, symbols) +local function buildBlock(source, sub, used, symbols) + if used[source] then + return + end + used[source] = true + if source.type == 'if' then + for _, block in ipairs(source) do + symbols[#symbols+1] = { + name = block.type:gsub('block$', ''), + detail = sub(block.start + 1, block.keyword[4] or block.keyword[2]), + kind = define.SymbolKind.Package, + range = { block.start, block.finish }, + valueRange = { block.start, block.finish }, + selectionRange = { block.keyword[1], block.keyword[2] }, + } + end + elseif source.type == 'while' then + symbols[#symbols+1] = { + name = 'while', + detail = sub(source.start + 1, source.keyword[4] or source.keyword[2]), + kind = define.SymbolKind.Package, + range = { source.start, source.finish }, + valueRange = { source.start, source.finish }, + selectionRange = { source.keyword[1], source.keyword[2] }, + } + elseif source.type == 'repeat' then + symbols[#symbols+1] = { + name = 'repeat', + detail = source.filter and sub(source.keyword[3] + 1, source.filter.finish) or '', + kind = define.SymbolKind.Package, + range = { source.start, source.finish }, + valueRange = { source.start, source.finish }, + selectionRange = { source.keyword[1], source.keyword[2] }, + } + elseif source.type == 'loop' + or source.type == 'in' then + symbols[#symbols+1] = { + name = 'for', + detail = sub(source.start, source.keyword[4] or source.keyword[2]), + kind = define.SymbolKind.Package, + range = { source.start, source.finish }, + valueRange = { source.start, source.finish }, + selectionRange = { source.keyword[1], source.keyword[2] }, + } + end +end + +local function buildSource(source, sub, used, symbols) if source.type == 'local' or source.type == 'setlocal' or source.type == 'setglobal' or source.type == 'setfield' or source.type == 'setmethod' - or source.type == 'tablefield' then - await.delay() - buildSet(source, text, used, symbols) - elseif source.type == 'function' then - await.delay() - buildAnonymousFunction(source, text, used, symbols) + or source.type == 'tablefield' + or source.type == 'tableexp' + or source.type == 'tableindex' then + buildValue(source, sub, used, symbols) + elseif source.type == 'function' + or source.type == 'table' then + buildAnonymous(source, sub, used, symbols) + elseif source.type == 'if' + or source.type == 'while' + or source.type == 'in' + or source.type == 'loop' + or source.type == 'repeat' then + buildBlock(source, sub, used, symbols) end end ---@async local function makeSymbol(uri) - local ast = files.getState(uri) - local text = files.getText(uri) - if not ast or not text then + local state = files.getState(uri) + if not state then return nil end + local sub = subber(state) local symbols = {} local used = {} - guide.eachSource(ast.ast, function (source) ---@async - buildSource(source, text, used, symbols) + local i = 0 + ---@async + guide.eachSource(state.ast, function (source) + buildSource(source, sub, used, symbols) + i = i + 1 + if i % 1000 == 0 then + await.delay() + end end) return symbols diff --git a/script/core/find-source.lua b/script/core/find-source.lua index 99013b31e..c5d52f3e4 100644 --- a/script/core/find-source.lua +++ b/script/core/find-source.lua @@ -11,10 +11,10 @@ local function isValidFunctionPos(source, offset) return false end -return function (ast, position, accept) +return function (state, position, accept) local len = math.huge local result - guide.eachSourceContain(ast.ast, position, function (source) + guide.eachSourceContain(state.ast, position, function (source) if source.type == 'function' then if not isValidFunctionPos(source, position) then return diff --git a/script/core/formatting.lua b/script/core/formatting.lua index fb5ca9c7e..de7f30ed9 100644 --- a/script/core/formatting.lua +++ b/script/core/formatting.lua @@ -1,8 +1,11 @@ -local codeFormat = require("code_format") local files = require("files") local log = require("log") return function(uri, options) + local suc, codeFormat = pcall(require, "code_format") + if not suc then + return + end local text = files.getOriginText(uri) local state = files.getState(uri) if not state then diff --git a/script/core/highlight.lua b/script/core/highlight.lua index edd8c95d6..722146724 100644 --- a/script/core/highlight.lua +++ b/script/core/highlight.lua @@ -7,7 +7,7 @@ local guide = require 'parser.guide' ---@async local function eachRef(source, callback) - local refs = vm.getRefs(source, function () + local refs = vm.getRefs(source, function (_) return false end) for _, ref in ipairs(refs) do @@ -63,7 +63,7 @@ local function checkInIf(state, source, text, position) local endA = endB - #'end' + 1 if position >= source.finish - #'end' and position <= source.finish - and text:sub(endA, endB) == 'end' then + and text and text:sub(endA, endB) == 'end' then return true end -- ๆฃ€ๆŸฅๆฏไธชๅญๆจกๅ— @@ -83,7 +83,7 @@ local function makeIf(state, source, text, callback) -- end local endB = guide.positionToOffset(state, source.finish) local endA = endB - #'end' + 1 - if text:sub(endA, endB) == 'end' then + if text and text:sub(endA, endB) == 'end' then callback(source.finish - #'end', source.finish) end -- ๆฏไธชๅญๆจกๅ— diff --git a/script/core/hint.lua b/script/core/hint.lua index 767e531e5..67ac85167 100644 --- a/script/core/hint.lua +++ b/script/core/hint.lua @@ -290,9 +290,9 @@ local function semicolonHint(uri, results, start, finish) for i = 1, #src - 1 do local current = src[i] local next = src[i+1] - local left = current.finish + local left = current.range or current.finish local right = next.start - local text = subber(left, right) + local text = subber(current.finish, right) if mode == 'All' then if not text:find '[,;]' then results[#results+1] = { diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index e11dd6c8f..0cbcc835c 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -7,6 +7,7 @@ local util = require 'utility' local guide = require 'parser.guide' local rpath = require 'workspace.require-path' local furi = require 'file-uri' +local wssymbol = require 'core.workspace-symbol' local function collectRequire(mode, literal, uri) local result, searchers @@ -125,6 +126,52 @@ local function getBindComment(source) return table.concat(lines, '\n') end +---@async +local function packSee(see) + local name = see.name[1] + local buf = {} + local target + for _, symbol in ipairs(wssymbol(name, guide.getUri(see))) do + if symbol.name == name then + target = symbol.source + break + end + end + if target then + local row, col = guide.rowColOf(target.start) + buf[#buf+1] = ('[%s](%s#%d#%d)'):format(name, guide.getUri(target), row + 1, col) + else + buf[#buf+1] = ('~%s~'):format(name) + end + if see.comment then + buf[#buf+1] = ' ' + buf[#buf+1] = see.comment.text + end + return table.concat(buf) +end + +---@async +local function lookUpDocSees(lines, docGroup) + local sees = {} + for _, doc in ipairs(docGroup) do + if doc.type == 'doc.see' then + sees[#sees+1] = doc + end + end + if #sees == 0 then + return + end + if #sees == 1 then + lines[#lines+1] = ('See: %s'):format(packSee(sees[1])) + return + end + lines[#lines+1] = 'See:' + for _, see in ipairs(sees) do + lines[#lines+1] = (' * %s'):format(packSee(see)) + end +end + +---@async local function lookUpDocComments(source) local docGroup = source.bindDocs if not docGroup then @@ -142,7 +189,10 @@ local function lookUpDocComments(source) for _, doc in ipairs(docGroup) do if doc.type == 'doc.comment' then lines[#lines+1] = normalizeComment(doc.comment.text, uri) - elseif doc.type == 'doc.type' then + elseif doc.type == 'doc.type' + or doc.type == 'doc.public' + or doc.type == 'doc.protected' + or doc.type == 'doc.private' then if doc.comment then lines[#lines+1] = normalizeComment(doc.comment.text, uri) end @@ -155,6 +205,7 @@ local function lookUpDocComments(source) if source.comment then lines[#lines+1] = normalizeComment(source.comment.text, uri) end + lookUpDocSees(lines, docGroup) if not lines or #lines == 0 then return nil end @@ -212,7 +263,7 @@ local function buildEnumChunk(docType, name, uri) (enum.default and '->') or (enum.additional and '+>') or ' |', - vm.viewObject(enum, uri) + vm.getInfer(enum):view(uri) ) if enum.comment then local first = true @@ -285,7 +336,7 @@ local function tryDocFieldComment(source) end end -local function getFunctionComment(source) +local function getFunctionComment(source, raw) local docGroup = source.bindDocs if not docGroup then return @@ -305,14 +356,14 @@ local function getFunctionComment(source) if doc.type == 'doc.comment' then local comment = normalizeComment(doc.comment.text, uri) md:add('md', comment) - elseif doc.type == 'doc.param' then + elseif doc.type == 'doc.param' and not raw then if doc.comment then md:add('md', ('@*param* `%s` โ€” %s'):format( doc.param[1], doc.comment.text )) end - elseif doc.type == 'doc.return' then + elseif doc.type == 'doc.return' and not raw then if hasReturnComment then local name = {} for _, rtn in ipairs(doc.returns) do @@ -349,10 +400,14 @@ local function getFunctionComment(source) return comment end -local function tryDocComment(source) +---@async +local function tryDocComment(source, raw) local md = markdown() + if source.value and source.value.type == 'function' then + source = source.value + end if source.type == 'function' then - local comment = getFunctionComment(source) + local comment = getFunctionComment(source, raw) md:add('md', comment) source = source.parent end @@ -373,7 +428,8 @@ local function tryDocComment(source) return result end -local function tryDocOverloadToComment(source) +---@async +local function tryDocOverloadToComment(source, raw) if source.type ~= 'doc.type.function' then return end @@ -382,7 +438,7 @@ local function tryDocOverloadToComment(source) or not doc.bindSource then return end - local md = tryDocComment(doc.bindSource) + local md = tryDocComment(doc.bindSource, raw) if md then return md end @@ -393,7 +449,8 @@ local function tyrDocParamComment(source) or source.type == 'getlocal' then source = source.node end - if source.type ~= 'local' then + if source.type ~= 'local' + and source.type ~= '...' then return end if source.parent.type ~= 'funcargs' then @@ -421,47 +478,72 @@ local function tryDocEnum(source) if not tbl then return end - local md = markdown() - md:add('lua', '{') - for _, field in ipairs(tbl) do - if field.type == 'tablefield' - or field.type == 'tableindex' then - if not field.value then - goto CONTINUE - end - local key = guide.getKeyName(field) - if not key then - goto CONTINUE - end - if field.value.type == 'integer' - or field.value.type == 'string' then - md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1])) + if vm.docHasAttr(source, 'key') then + local md = markdown() + local keys = {} + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE + end + keys[#keys+1] = ('%q'):format(key) + ::CONTINUE:: end - if field.value.type == 'binary' - or field.value.type == 'unary' then - local number = vm.getNumber(field.value) - if number then - md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number)) + end + md:add('lua', table.concat(keys, ' | ')) + return md:string() + else + local md = markdown() + md:add('lua', '{') + for _, field in ipairs(tbl) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if not field.value then + goto CONTINUE + end + local key = guide.getKeyName(field) + if not key then + goto CONTINUE end + if field.value.type == 'integer' + or field.value.type == 'string' then + md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1])) + end + if field.value.type == 'binary' + or field.value.type == 'unary' then + local number = vm.getNumber(field.value) + if number then + md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number)) + end + end + ::CONTINUE:: end - ::CONTINUE:: end + md:add('lua', '}') + return md:string() end - md:add('lua', '}') - return md:string() end -return function (source) +---@async +return function (source, raw) if source.type == 'string' then return asString(source) end + if source.type == 'doc.tailcomment' then + return source.text + end if source.type == 'field' then source = source.parent end - return tryDocOverloadToComment(source) + return tryDocOverloadToComment(source, raw) or tryDocFieldComment(source) or tyrDocParamComment(source) - or tryDocComment(source) + or tryDocComment(source, raw) or tryDocClassComment(source) or tryDocModule(source) or tryDocEnum(source) diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua index 5a65cbce1..ed596b3d5 100644 --- a/script/core/hover/init.lua +++ b/script/core/hover/init.lua @@ -6,6 +6,7 @@ local util = require 'utility' local findSource = require 'core.find-source' local markdown = require 'provider.markdown' local guide = require 'parser.guide' +local wssymbol = require 'core.workspace-symbol' ---@async local function getHover(source) @@ -14,6 +15,15 @@ local function getHover(source) local labelMark = {} local descMark = {} + if source.type == 'doc.see.name' then + for _, symbol in ipairs(wssymbol(source[1], guide.getUri(source))) do + if symbol.name == source[1] then + source = symbol.source + break + end + end + end + ---@async local function addHover(def, checkLable, oop) if defMark[def] then @@ -60,8 +70,12 @@ local function getHover(source) if guide.isOOP(def) then oop = true end - if def.type == 'function' - or def.type == 'doc.type.function' then + if def.type == 'function' + and not vm.isVarargFunctionWithOverloads(def) then + hasFunc = true + addHover(def, true, oop) + end + if def.type == 'doc.type.function' then hasFunc = true addHover(def, true, oop) end @@ -72,7 +86,8 @@ local function getHover(source) else addHover(source, true, oop) for _, def in ipairs(vm.getDefs(source)) do - if def.type == 'global' then + if def.type == 'global' + or def.type == 'setlocal' then goto CONTINUE end if guide.isOOP(def) then @@ -107,6 +122,7 @@ local accept = { ['doc.enum.name'] = true, ['function'] = true, ['doc.module'] = true, + ['doc.see.name'] = true, } ---@async @@ -122,10 +138,18 @@ local function getHoverByUri(uri, position) local hover = getHover(source) if SHOWSOURCE then hover:splitLine() + hover:add('md', 'Source Info') hover:add('lua', util.dump(source, { deep = 1, })) end + if SHOWNODE then + hover:splitLine() + hover:add('md', 'Node Info') + hover:add('lua', util.dump(vm.compileNode(source), { + deep = 1, + })) + end return hover, source end diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index 5c502ec10..62e519272 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -58,8 +58,7 @@ local function asValue(source, title) and ( type == 'table' or type == 'any' or type == 'unknown' - or type == 'nil' - or type:sub(1, 1) == '{') then + or type == 'nil') then else pack[#pack+1] = type end @@ -135,7 +134,7 @@ local function asField(source) end local function asDocFieldName(source) - local name = source.field[1] + local name = vm.viewKey(source, guide.getUri(source)) or '?' local class for _, doc in ipairs(source.bindGroup) do if doc.type == 'doc.class' then @@ -144,10 +143,12 @@ local function asDocFieldName(source) end end local view = vm.getInfer(source.extends):view(guide.getUri(source)) - if not class then - return ('(field) ?.%s: %s'):format(name, view) + local className = class and class.class[1] or '?' + if name:match(guide.namePatternFull) then + return ('(field) %s.%s: %s'):format(className, name, view) + else + return ('(field) %s%s: %s'):format(className, name, view) end - return ('(field) %s.%s: %s'):format(class.class[1], name, view) end local function asString(source) diff --git a/script/core/hover/return.lua b/script/core/hover/return.lua index b496990b5..b71b9e5d6 100644 --- a/script/core/hover/return.lua +++ b/script/core/hover/return.lua @@ -1,36 +1,6 @@ local vm = require 'vm.vm' local guide = require 'parser.guide' ----@param source parser.object ----@return integer -local function countReturns(source) - local n = 0 - - 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 - if rtn.returnIndex and rtn.returnIndex > n then - n = rtn.returnIndex - end - end - end - end - end - - local returns = source.returns - if returns then - for _, rtn in ipairs(returns) do - if #rtn > n then - n = #rtn - end - end - end - - return n -end - ---@param source parser.object ---@return parser.object[] local function getReturnDocs(source) @@ -51,7 +21,7 @@ local function getReturnDocs(source) end local function asFunction(source) - local num = countReturns(source) + local _, _, num = vm.countReturnsOfFunction(source) if num == 0 then return nil end diff --git a/script/core/hover/table.lua b/script/core/hover/table.lua index 677fd76cb..e59a26d05 100644 --- a/script/core/hover/table.lua +++ b/script/core/hover/table.lua @@ -4,17 +4,6 @@ local config = require 'config' local await = require 'await' local guide = require 'parser.guide' -local function formatKey(key) - if type(key) == 'string' then - if key:match '^[%a_][%w_]*$' then - return key - else - return ('[%s]'):format(util.viewLiteral(key)) - end - end - return ('[%s]'):format(key) -end - ---@param uri uri ---@param keys string[] ---@param nodeMap table @@ -34,14 +23,14 @@ local function buildAsHash(uri, keys, nodeMap, reachMax) local literalView = ifr:viewLiterals() if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( - formatKey(key), + key, isOptional and '?' or '', typeView, literalView ) else lines[#lines+1] = (' %s%s: %s,'):format( - formatKey(key), + key, isOptional and '?' or '', typeView ) @@ -79,14 +68,14 @@ local function buildAsConst(uri, keys, nodeMap, reachMax) local literalView = literalMap[key] if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( - formatKey(key), + key, isOptional and '?' or '', typeView, literalView ) else lines[#lines+1] = (' %s%s: %s,'):format( - formatKey(key), + key, isOptional and '?' or '', typeView ) @@ -99,61 +88,62 @@ local function buildAsConst(uri, keys, nodeMap, reachMax) return table.concat(lines, '\n') end -local typeSorter = { - ['string'] = 1, - ['number'] = 2, - ['boolean'] = 3, -} - -local function getKeyMap(fields) +---@param source parser.object +---@param fields parser.object[] +local function getVisibleKeyMap(source, fields) + local uri = guide.getUri(source) local keys = {} local map = {} + local ignored = {} for _, field in ipairs(fields) do - local key = vm.getKeyName(field) - if key and not map[key] then - map[key] = true - keys[#keys+1] = key + local key = vm.viewKey(field, uri) + local rawKey = guide.getKeyName(field) + if rawKey and rawKey ~= key then + ignored[rawKey] = true + map[rawKey] = nil + end + if not ignored[key] + and vm.isVisible(source, field) then + if key and not map[key] then + map[key] = true + end end end + for key in pairs(map) do + keys[#keys+1] = key + end table.sort(keys, function (a, b) if a == b then return false end - local ta = type(a) - local tb = type(b) - local tsa = typeSorter[ta] - local tsb = typeSorter[tb] - if tsa == tsb then - if ta == 'boolean' then - return a == true - end - if ta == 'string' then - if a:sub(1, 1) == '_' then - if b:sub(1, 1) == '_' then - return a < b - else - return false - end - elseif b:sub(1, 1) == '_' then - return true - else - return a < b - end - end + local s1 = 0 + local s2 = 0 + if a:sub(1, 1) == '_' then + s1 = s1 + 10 + end + if b:sub(1, 1) == '_' then + s2 = s2 + 10 + end + if a:sub(1, 1) == '[' then + s1 = s1 + 1 + end + if b:sub(1, 1) == '[' then + s2 = s2 + 1 + end + if s1 == s2 then return a < b - else - return tsa < tsb end + return s1 < s2 end) return keys, map end ---@async -local function getNodeMap(fields, keyMap) +local function getNodeMap(uri, fields, keyMap) ---@type table local nodeMap = {} for _, field in ipairs(fields) do - local key = vm.getKeyName(field) + local key = vm.viewKey(field, uri) if not key or not keyMap[key] then goto CONTINUE end @@ -188,11 +178,13 @@ return function (source) elseif n.type == 'doc.type.string' or n.type == 'string' then return nil + elseif n.type == 'doc.type.sign' then + return nil end end local fields = vm.getFields(source) - local keys, map = getKeyMap(fields) + local keys, map = getVisibleKeyMap(source, fields) if #keys == 0 then return nil end @@ -205,7 +197,7 @@ return function (source) end end - local nodeMap = getNodeMap(fields, map) + local nodeMap = getNodeMap(uri, fields, map) local isConsts = true for i = 1, #keys do diff --git a/script/core/look-backward.lua b/script/core/look-backward.lua index 8d3e34397..508b57eb0 100644 --- a/script/core/look-backward.lua +++ b/script/core/look-backward.lua @@ -37,7 +37,7 @@ end function m.findWord(text, offset) for i = offset, 1, -1 do - if not text:sub(i, i):match '[%w_]' then + if not text:sub(i, i):match '[%w_\x80-\xff]' then if i == offset then return nil end @@ -58,7 +58,8 @@ function m.findSymbol(text, offset) or char == '(' or char == ',' or char == '[' - or char == '=' then + or char == '=' + or char == '{' then return char, i else return nil diff --git a/script/core/modifyRequirePath.lua b/script/core/modifyRequirePath.lua new file mode 100644 index 000000000..d0783736e --- /dev/null +++ b/script/core/modifyRequirePath.lua @@ -0,0 +1,74 @@ +local files = require 'files' +local await = require 'await' +local guide = require 'parser.guide' +local rpath = require 'workspace.require-path' +local furi = require 'file-uri' +local util = require 'utility' +local client = require 'client' +local lang = require 'language' + +---@alias rename { oldUri: uri, newUri: uri } + +---@param changes table[] +---@param uri uri +---@param renames rename[] +local function checkConvert(changes, uri, renames) + local state = files.getState(uri) + if not state then + return + end + + guide.eachSpecialOf(state.ast, 'require', function (source) + local call = source.parent + if call.type ~= 'call' then + return + end + local nameObj = call.args and call.args[1] + if not nameObj then + return + end + local name = nameObj.type == 'string' and nameObj[1] + if type(name) ~= 'string' then + return + end + for _, rename in ipairs(renames) do + if rpath.isMatchedUri(uri, rename.oldUri, name) then + local visibles = rpath.getVisiblePath(uri, furi.decode(rename.newUri)) + if #visibles > 0 then + local newName = visibles[1].name + changes[#changes+1] = { + uri = uri, + start = nameObj.start, + finish = nameObj.finish, + text = util.viewString(newName, nameObj[2]), + } + return + end + end + end + end) +end + +---@async +---@param renames rename[] +return function (renames) + if #renames == 0 then + return + end + local changes = {} + for uri in files.eachFile() do + checkConvert(changes, uri, renames) + await.delay() + end + if #changes == 0 then + return + end + + local _, index = client.awaitRequestMessage('Info', lang.script.WINDOW_MODIFY_REQUIRE_PATH, { + lang.script.WINDOW_MODIFY_REQUIRE_OK + }) + + if index == 1 then + client.editMultiText(changes) + end +end diff --git a/script/core/rangeformatting.lua b/script/core/rangeformatting.lua index f64e9cda3..7c3953e6b 100644 --- a/script/core/rangeformatting.lua +++ b/script/core/rangeformatting.lua @@ -1,10 +1,17 @@ -local codeFormat = require("code_format") local files = require("files") local log = require("log") local converter = require("proto.converter") return function(uri, range, options) - local text = files.getOriginText(uri) + local state = files.getState(uri) + if not state then + return + end + local suc, codeFormat = pcall(require, "code_format") + if not suc then + return + end + local text = state.originText local status, formattedText, startLine, endLine = codeFormat.range_format( uri, text, range.start.line, range["end"].line, options) @@ -18,8 +25,8 @@ return function(uri, range, options) return { { - start = converter.unpackPosition(uri, { line = startLine, character = 0 }), - finish = converter.unpackPosition(uri, { line = endLine + 1, character = 0 }), + start = converter.unpackPosition(state, { line = startLine, character = 0 }), + finish = converter.unpackPosition(state, { line = endLine + 1, character = 0 }), text = formattedText, } } diff --git a/script/core/reference.lua b/script/core/reference.lua index 6f67a9de1..e24b5217b 100644 --- a/script/core/reference.lua +++ b/script/core/reference.lua @@ -51,10 +51,14 @@ local accept = { ['doc.extends.name'] = true, ['doc.alias.name'] = true, ['doc.enum.name'] = true, + ['doc.field.name'] = true, } ---@async -return function (uri, position) +---@param uri uri +---@param position integer +---@param includeDeclaration boolean +return function (uri, position, includeDeclaration) local ast = files.getState(uri) if not ast then return nil @@ -81,6 +85,12 @@ return function (uri, position) if src.type == 'self' then goto CONTINUE end + if not includeDeclaration then + if guide.isAssign(src) + or guide.isLiteral(src) then + goto CONTINUE + end + end src = src.field or src.method or src if src.type == 'getindex' or src.type == 'setindex' @@ -106,6 +116,9 @@ return function (uri, position) if src.type == 'doc.enum' then src = src.enum end + if src.type == 'doc.type.field' then + src = src.name + end if src.type == 'doc.class.name' or src.type == 'doc.alias.name' or src.type == 'doc.enum.name' @@ -115,7 +128,8 @@ return function (uri, position) and source.type ~= 'doc.class.name' and source.type ~= 'doc.enum.name' and source.type ~= 'doc.extends.name' - and source.type ~= 'doc.see.name' then + and source.type ~= 'doc.see.name' + and source.type ~= 'doc.alias.name' then goto CONTINUE end end diff --git a/script/core/rename.lua b/script/core/rename.lua index 90e662246..534a972a8 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -3,42 +3,59 @@ local vm = require 'vm' local util = require 'utility' local findSource = require 'core.find-source' local guide = require 'parser.guide' +local config = require 'config' local Forcing +---@param str string +---@return string local function trim(str) return str:match '^%s*(%S+)%s*$' end -local function isValidName(str) +---@param uri uri +---@param str string +---@return boolean +local function isValidName(uri, str) if not str then return false end - return str:match '^[%a_][%w_]*$' + local allowUnicode = config.get(uri, 'Lua.runtime.unicodeName') + if allowUnicode then + return str:match '^[%a_\x80-\xff][%w_\x80-\xff]*$' + else + return str:match '^[%a_][%w_]*$' + end end -local function isValidGlobal(str) +---@param uri uri +---@param str string +---@return boolean +local function isValidGlobal(uri, str) if not str then return false end for s in str:gmatch '[^%.]*' do - if not isValidName(trim(s)) then + if not isValidName(uri, trim(s)) then return false end end return true end -local function isValidFunctionName(str) - if isValidGlobal(str) then +---@param uri uri +---@param str string +---@return boolean +local function isValidFunctionName(uri, str) + if isValidGlobal(uri, str) then return true end local offset = str:find(':', 1, true) if not offset then return false end - return isValidGlobal(trim(str:sub(1, offset-1))) - and isValidName(trim(str:sub(offset+1))) + return isValidGlobal(uri, trim(str:sub(1, offset-1))) + and isValidName(uri, trim(str:sub(offset+1))) end local function isFunctionGlobalName(source) @@ -54,7 +71,7 @@ local function isFunctionGlobalName(source) end local function renameLocal(source, newname, callback) - if isValidName(newname) then + if isValidName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return end @@ -62,7 +79,7 @@ local function renameLocal(source, newname, callback) end local function renameField(source, newname, callback) - if isValidName(newname) then + if isValidName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end @@ -108,11 +125,11 @@ local function renameField(source, newname, callback) end local function renameGlobal(source, newname, callback) - if isValidGlobal(newname) then + if isValidGlobal(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end - if isValidFunctionName(newname) then + if isValidFunctionName(guide.getUri(source), newname) then callback(source, source.start, source.finish, newname) return true end @@ -178,6 +195,11 @@ local function ofFieldThen(key, src, newname, callback) if not suc then return end + elseif src.type == 'doc.field' then + local suc = renameField(src.field, newname, callback) + if not suc then + return + end end end @@ -200,12 +222,9 @@ local function ofGlobal(source, newname, callback) 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) + local refs = vm.getRefs(source) + for _, ref in ipairs(refs) do + ofFieldThen(key, ref, newname, callback) end end @@ -235,8 +254,10 @@ local function ofDocTypeName(source, newname, callback) callback(doc, doc.enum.start, doc.enum.finish, newname) end end - for _, doc in ipairs(global:getGets(uri)) do - if doc.type == 'doc.type.name' then + local refs = vm.getRefs(source) + for _, doc in ipairs(refs) do + if doc.type == 'doc.type.name' + or doc.type == 'doc.extends.name' then callback(doc, doc.start, doc.finish, newname) end end @@ -280,10 +301,13 @@ local function rename(source, newname, callback) elseif source.type == 'doc.class.name' or source.type == 'doc.type.name' or source.type == 'doc.alias.name' - or source.type == 'doc.enum.name' then + or source.type == 'doc.enum.name' + or source.type == 'doc.extends.name' then return ofDocTypeName(source, newname, callback) elseif source.type == 'doc.param.name' then return ofDocParamName(source, newname, callback) + elseif source.type == 'doc.field.name' then + return ofField(source, newname, callback) elseif source.type == 'string' or source.type == 'number' or source.type == 'integer' @@ -315,7 +339,9 @@ local function prepareRename(source) or source.type == 'doc.type.name' or source.type == 'doc.alias.name' or source.type == 'doc.enum.name' - or source.type == 'doc.param.name' then + or source.type == 'doc.param.name' + or source.type == 'doc.field.name' + or source.type == 'doc.extends.name' then return source, source[1] elseif source.type == 'string' or source.type == 'number' @@ -351,11 +377,13 @@ local accept = { ['number'] = true, ['integer'] = true, - ['doc.class.name'] = true, - ['doc.type.name'] = true, - ['doc.alias.name'] = true, - ['doc.param.name'] = true, - ['doc.enum.name'] = true, + ['doc.class.name'] = true, + ['doc.type.name'] = true, + ['doc.alias.name'] = true, + ['doc.param.name'] = true, + ['doc.enum.name'] = true, + ['doc.field.name'] = true, + ['doc.extends.name'] = true, } local m = {} @@ -386,7 +414,11 @@ function m.rename(uri, pos, newname) return end mark[uid] = true - if files.isLibrary(turi, true) then + if vm.isMetaFile(turi) then + return + end + if files.isLibrary(turi, true) + and not files.isLibrary(uri, true) then return end results[#results+1] = { diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 5833807bf..e908ef7b5 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -7,6 +7,7 @@ local guide = require 'parser.guide' local converter = require 'proto.converter' local config = require 'config' local linkedTable = require 'linked-table' +local client = require 'client' local Care = util.switch() : case 'getglobal' @@ -35,7 +36,7 @@ local Care = util.switch() local isFunc = vm.getInfer(source):hasFunction(guide.getUri(source)) local type = isFunc and define.TokenTypes['function'] or define.TokenTypes.variable - local modifier = isLib and define.TokenModifiers.defaultLibrary or define.TokenModifiers.static + local modifier = isLib and define.TokenModifiers.defaultLibrary or define.TokenModifiers.global results[#results+1] = { start = source.start, @@ -65,18 +66,21 @@ local Care = util.switch() if not options.variable then return end - local modifiers = 0 - if source.parent and source.parent.type == 'tablefield' then - modifiers = define.TokenModifiers.declaration - end if source.parent then + if source.parent.type == 'tablefield' then + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.property, + } + return + end local value = source.parent.value if value and value.type == 'function' then results[#results+1] = { start = source.start, finish = source.finish, type = define.TokenTypes.method, - modifieres = modifiers, } return end @@ -86,7 +90,6 @@ local Care = util.switch() start = source.start, finish = source.finish, type = define.TokenTypes.method, - modifieres = modifiers, } return end @@ -95,7 +98,6 @@ local Care = util.switch() start = source.start, finish = source.finish, type = define.TokenTypes.method, - modifieres = modifiers, } return end @@ -103,7 +105,6 @@ local Care = util.switch() start = source.start, finish = source.finish, type = define.TokenTypes.property, - modifieres = modifiers, } end) : case 'local' @@ -137,12 +138,20 @@ local Care = util.switch() local uri = guide.getUri(loc) -- 1. ๅ€ผไธบๅ‡ฝๆ•ฐ็š„ๅฑ€้ƒจๅ˜้‡ | Local variable whose value is a function if vm.getInfer(source):hasFunction(uri) then - results[#results+1] = { - start = source.start, - finish = source.finish, - type = define.TokenTypes['function'], - modifieres = define.TokenModifiers.declaration, - } + if source.type == 'local' then + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes['function'], + modifieres = define.TokenModifiers.declaration, + } + else + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes['function'], + } + end return end -- 3. ็‰นๆฎŠๅ˜้‡ | Special variableif source[1] == '_ENV' then @@ -198,7 +207,7 @@ local Care = util.switch() start = source.start, finish = source.finish, type = define.TokenTypes['function'], - modifieres = guide.isSet(source) and define.TokenModifiers.declaration or nil, + modifieres = guide.isAssign(source) and define.TokenModifiers.declaration or nil, } return end @@ -437,6 +446,13 @@ local Care = util.switch() type = define.TokenTypes.type, modifieres = define.TokenModifiers.modification, } + elseif source[1] == 'self' then + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.type, + modifieres = define.TokenModifiers.readonly, + } else results[#results+1] = { start = source.start, @@ -592,17 +608,6 @@ local Care = util.switch() type = define.TokenTypes.class, } end) - : case 'doc.see.field' - : call(function (source, options, results) - if not options.annotation then - return - end - results[#results+1] = { - start = source.start, - finish = source.finish, - type = define.TokenTypes.property, - } - end) : case 'doc.diagnostic' : call(function (source, options, results) if not options.annotation then @@ -698,32 +703,56 @@ local Care = util.switch() type = define.TokenTypes.operator, } end) + : case 'doc.meta.name' + : call(function (source, options, results) + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.namespace, + } + end) + : case 'doc.attr' + : call(function (source, options, results) + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.decorator, + } + end) -local function buildTokens(uri, results) +---@param state table +---@param results table +local function buildTokens(state, results) local tokens = {} local lastLine = 0 local lastStartChar = 0 + local index = 0 for i, source in ipairs(results) do - local startPos = converter.packPosition(uri, source.start) - local finishPos = converter.packPosition(uri, source.finish) + local startPos = source.start + local finishPos = source.finish local line = startPos.line local startChar = startPos.character local deltaLine = line - lastLine local deltaStartChar if deltaLine == 0 then deltaStartChar = startChar - lastStartChar + if deltaStartChar == 0 and i > 1 then + goto continue + end else deltaStartChar = startChar end lastLine = line lastStartChar = startChar -- see https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#textDocument_semanticTokens - local len = i * 5 - 5 + index = index + 1 + local len = index * 5 - 5 tokens[len + 1] = deltaLine tokens[len + 2] = deltaStartChar tokens[len + 3] = finishPos.character - startPos.character -- length tokens[len + 4] = source.type tokens[len + 5] = source.modifieres or 0 + ::continue:: end return tokens end @@ -786,28 +815,45 @@ local function solveMultilineAndOverlapping(state, results) local new = {} for token in tokens:pairs() do - new[#new+1] = token - local startRow, startCol = guide.rowColOf(token.start) - local finishRow, finishCol = guide.rowColOf(token.finish) - if finishRow > startRow then - token.finish = guide.positionOf(startRow, guide.getLineRange(state, startRow)) - for i = startRow + 1, finishRow - 1 do + local startPos = converter.packPosition(state, token.start) + local endPos = converter.packPosition(state, token.finish) + if startPos.line == endPos.line + and startPos.character == endPos.character then + goto continue + end + if endPos.line == startPos.line + or client.getAbility 'textDocument.semanticTokens.multilineTokenSupport' then + new[#new+1] = { + start = startPos, + finish = endPos, + type = token.type, + modifieres = token.modifieres, + } + else + new[#new+1] = { + start = startPos, + finish = converter.position(startPos.line, 9999), + type = token.type, + modifieres = token.modifieres, + } + for i = startPos.line + 1, endPos.line - 1 do new[#new+1] = { - start = guide.positionOf(i, 0), - finish = guide.positionOf(i, guide.getLineRange(state, i)), + start = converter.position(i, 0), + finish = converter.position(i, 9999), type = token.type, modifieres = token.modifieres, } end - if finishCol > 0 then + if endPos.character > 0 then new[#new+1] = { - start = guide.positionOf(finishRow, 0), - finish = guide.positionOf(finishRow, finishCol), + start = converter.position(endPos.line, 0), + finish = converter.position(endPos.line, endPos.character), type = token.type, modifieres = token.modifieres, } end end + ::continue:: end return new @@ -836,6 +882,10 @@ return function (uri, start, finish) local n = 0 guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async + -- skip virtual source + if source.virtual then + return + end Care(source.type, source, options, results) n = n + 1 if n % 100 == 0 then @@ -844,6 +894,10 @@ return function (uri, start, finish) end) for _, comm in ipairs(state.comms) do + -- skip virtual comment + if comm.virtual then + return + end if start <= comm.start and comm.finish <= finish then local headPos = (comm.type == 'comment.short' and comm.text:match '^%-%s*[@|]()') or (comm.type == 'comment.long' and comm.text:match '^@()') @@ -881,7 +935,7 @@ return function (uri, start, finish) results = solveMultilineAndOverlapping(state, results) - local tokens = buildTokens(uri, results) + local tokens = buildTokens(state, results) return tokens end diff --git a/script/core/signature.lua b/script/core/signature.lua index 21e954bf9..98018b215 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -60,6 +60,9 @@ local function makeOneSignature(source, oop, index) : gsub('%b()', function (str) return ('_'):rep(#str) end) + : gsub('%b{}', function (str) + return ('_'):rep(#str) + end) : gsub('[%[%]%(%)]', '_') for start, finish in converted:gmatch '%s*()[^,]+()' do i = i + 1 @@ -86,6 +89,39 @@ local function makeOneSignature(source, oop, index) } end +local function isEventNotMatch(call, src) + if not call.args or not src.args then + return false + end + local literal, index + for i = 1, 2 do + if not call.args[i] then + break + end + literal = guide.getLiteral(call.args[i]) + if literal then + index = i + break + end + end + if not literal then + return false + end + local event = src.args[index] + if not event or event.type ~= 'doc.type.arg' then + return false + end + if not event.extends + or #event.extends.types ~= 1 then + return false + end + local eventLiteral = event.extends.types[1] and guide.getLiteral(event.extends.types[1]) + if eventLiteral == nil then + return false + end + return eventLiteral ~= literal +end + ---@async local function makeSignatures(text, call, pos) local func = call.node @@ -131,15 +167,30 @@ local function makeSignatures(text, call, pos) local signs = {} local node = vm.compileNode(func) ---@type vm.node - node = node:getData 'originNode' or node + node = node.originNode or node local mark = {} for src in node:eachObject() do - if src.type == 'function' + if (src.type == 'function' and not vm.isVarargFunctionWithOverloads(src)) or src.type == 'doc.type.function' then - if not mark[src] then + if not mark[src] + and not isEventNotMatch(call, src) then mark[src] = true signs[#signs+1] = makeOneSignature(src, oop, index) end + elseif src.type == 'global' and src.cate == 'type' then + ---@cast src vm.global + for _, set in ipairs(src:getSets(guide.getUri(call))) do + if set.type == 'doc.class' then + for _, overload in ipairs(set.calls) do + local f = overload.overload + if not mark[f] + and not isEventNotMatch(call, src) then + mark[f] = true + signs[#signs+1] = makeOneSignature(f, oop, index) + end + end + end + end end end return signs diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index a1c2b29f3..0a821f25f 100644 --- a/script/core/type-definition.lua +++ b/script/core/type-definition.lua @@ -54,7 +54,6 @@ local accept = { ['doc.alias.name'] = true, ['doc.enum.name'] = true, ['doc.see.name'] = true, - ['doc.see.field'] = true, } local function checkRequire(source, offset) diff --git a/script/core/type-formatting.lua b/script/core/type-formatting.lua index 0c326b8bd..419cb56b9 100644 --- a/script/core/type-formatting.lua +++ b/script/core/type-formatting.lua @@ -1,6 +1,9 @@ local files = require 'files' local lookBackward = require 'core.look-backward' -local guide = require "parser.guide" +local guide = require 'parser.guide' +local config = require 'config' +local util = require 'utility' + local function insertIndentation(uri, position, edits) local text = files.getText(uri) @@ -86,15 +89,155 @@ local function checkSplitOneLine(results, uri, position, ch) end end -return function (uri, position, ch) - local ast = files.getState(uri) - if not ast then +local function getIndent(state, row) + local offset = state.lines[row] + local indent = state.lua:match('^[\t ]*', offset) + return indent +end + +local function isInBlock(state, position) + local block = guide.eachSourceContain(state.ast, position, function(source) + if source.type == 'ifblock' + or source.type == 'elseifblock' then + if source.keyword[4] and source.keyword[4] <= position then + return true + end + end + if source.type == 'else' then + if source.keyword[2] and source.keyword[2] <= position then + return true + end + end + if source.type == 'while' then + if source.keyword[4] and source.keyword[4] <= position then + return true + end + end + if source.type == 'repeat' then + if source.keyword[2] and source.keyword[2] <= position then + return true + end + end + if source.type == 'loop' then + if source.keyword[4] and source.keyword[4] <= position then + return true + end + end + if source.type == 'in' then + if source.keyword[6] and source.keyword[6] <= position then + return true + end + end + if source.type == 'do' then + if source.keyword[2] and source.keyword[2] <= position then + return true + end + end + if source.type == 'function' then + if source.args and source.args.finish <= position then + return true + end + if not source.keyword[3] or source.keyword[3] >= position then + return true + end + end + if source.type == 'table' then + if source.start + 1 == position then + return true + end + end + end) + return block ~= nil +end + +local function checkWrongIndentation(results, uri, position, ch) + if ch ~= '\n' then + return + end + local state = files.getState(uri) + if not state then + return + end + local row = guide.rowColOf(position) + if row <= 0 then + return + end + local myIndent = getIndent(state, row) + local lastIndent = getIndent(state, row - 1) + if #myIndent <= #lastIndent then + return + end + if not util.stringStartWith(myIndent, lastIndent) then + return + end + local lastOffset = lookBackward.findAnyOffset(state.lua, guide.positionToOffset(state, position) - 1) + if not lastOffset then + return + end + local lastPosition = guide.offsetToPosition(state, lastOffset) + if isInBlock(state, lastPosition) then + return + end + results[#results+1] = { + start = position - #myIndent + #lastIndent, + finish = position, + text = '', + } +end + +local function typeFormat(results, uri, position, ch, options) + if ch ~= '\n' then + return + end + local suc, codeFormat = pcall(require, "code_format") + if not suc then + return + end + local text = files.getOriginText(uri) + local state = files.getState(uri) + if not state then + return + end + local converter = require("proto.converter") + local pos = converter.packPosition(state, position) + local typeFormatOptions = config.get(uri, 'Lua.typeFormat.config') + local success, result = codeFormat.type_format(uri, text, pos.line, pos.character, options, typeFormatOptions) + if success then + local range = result.range + results[#results+1] = { + text = result.newText, + start = converter.unpackPosition(state, { line = range.start.line, character = range.start.character }), + finish = converter.unpackPosition(state, { line = range["end"].line, character = range["end"].character }), + } + end +end + +return function (uri, position, ch, options) + local state = files.getState(uri) + if not state then return nil end local results = {} -- split `function () $ end` checkSplitOneLine(results, uri, position, ch) + if #results > 0 then + return results + end + + checkWrongIndentation(results, uri, position, ch) + if #results > 0 then + return results + end - return results + if TEST then + return nil + end + + typeFormat(results, uri, position, ch, options) + if #results > 0 then + return results + end + + return nil end diff --git a/script/core/view/psi-select.lua b/script/core/view/psi-select.lua new file mode 100644 index 000000000..406420393 --- /dev/null +++ b/script/core/view/psi-select.lua @@ -0,0 +1,13 @@ +local files = require("files") +local guide = require("parser.guide") +local converter = require("proto.converter") + +return function(uri, position) + local state = files.getState(uri) + if not state then + return + end + + local pos = converter.unpackPosition(state, position) + return { data = guide.positionToOffset(state, pos) } +end diff --git a/script/core/view/psi-view.lua b/script/core/view/psi-view.lua new file mode 100644 index 000000000..640d1c4c0 --- /dev/null +++ b/script/core/view/psi-view.lua @@ -0,0 +1,96 @@ +local files = require("files") +local guide = require("parser.guide") +local converter = require("proto.converter") +local subString = require 'core.substring' + + + +---@class psi.view.node +---@field name string +---@field attr? psi.view.attr +---@field children? psi.view.node[] + +---@class psi.view.attr +---@field range psi.view.range + +---@class psi.view.range +---@field start integer +---@field end integer + +---@param astNode parser.object +---@param state parser.state +---@return psi.view.node | nil +local function toPsiNode(astNode, state) + if not astNode or not astNode.start then + return + end + + local startOffset = guide.positionToOffset(state, astNode.start) + local finishOffset = guide.positionToOffset(state, astNode.finish) + local startPosition = converter.packPosition(state, astNode.start) + local finishPosition = converter.packPosition(state, astNode.finish) + return { + name = string.format("%s@[%d:%d .. %d:%d]", + astNode.type, + startPosition.line + 1, startPosition.character + 1, + finishPosition.line + 1, finishPosition.character + 1), + attr = { + range = { + start = startOffset, + ["end"] = finishOffset + } + } + } +end + +---@param astNode parser.object +---@return psi.view.node | nil +local function collectPsi(astNode, state) + local psiNode = toPsiNode(astNode, state) + + if not psiNode then + return + end + + guide.eachChild(astNode, function(child) + if psiNode.children == nil then + psiNode.children = {} + end + + local psi = collectPsi(child, state) + if psi then + psiNode.children[#psiNode.children+1] = psi + end + end) + + if psiNode.children and #psiNode.children > 0 and psiNode.attr then + local range = psiNode.attr.range + if range.start > psiNode.children[1].attr.range.start then + range.start = psiNode.children[1].attr.range.start + end + if range["end"] < psiNode.children[#psiNode.children].attr.range["end"] then + range["end"] = psiNode.children[#psiNode.children].attr.range["end"] + end + end + + if not psiNode.children then + local subber = subString(state) + local showText = subber(astNode.start + 1, astNode.finish) + if string.len(showText) > 30 then + showText = showText:sub(0, 30).. " ... " + end + + psiNode.name = psiNode.name .. " " .. showText + end + + return psiNode +end + +return function(uri) + local state = files.getState(uri) + if not state then + return + end + + return { data = collectPsi(state.ast, state) } +end diff --git a/script/core/workspace-symbol.lua b/script/core/workspace-symbol.lua index 9dd768db6..a41cfc1dd 100644 --- a/script/core/workspace-symbol.lua +++ b/script/core/workspace-symbol.lua @@ -3,6 +3,7 @@ local guide = require 'parser.guide' local matchKey = require 'core.matchkey' local define = require 'proto.define' local await = require 'await' +local vm = require 'vm' local function buildSource(uri, source, key, results) if source.type == 'local' @@ -11,10 +12,10 @@ local function buildSource(uri, source, key, results) local name = source[1] if matchKey(key, name) then results[#results+1] = { - name = name, - kind = define.SymbolKind.Variable, - uri = uri, - range = { source.start, source.finish }, + name = name, + skind = define.SymbolKind.Variable, + ckind = define.CompletionItemKind.Variable, + source = source, } end elseif source.type == 'setfield' @@ -23,10 +24,10 @@ local function buildSource(uri, source, key, results) local name = field and field[1] if name and matchKey(key, name) then results[#results+1] = { - name = name, - kind = define.SymbolKind.Field, - uri = uri, - range = { field.start, field.finish }, + name = name, + skind = define.SymbolKind.Field, + ckind = define.CompletionItemKind.Field, + source = field, } end elseif source.type == 'setmethod' then @@ -34,10 +35,10 @@ local function buildSource(uri, source, key, results) local name = method and method[1] if name and matchKey(key, name) then results[#results+1] = { - name = name, - kind = define.SymbolKind.Method, - uri = uri, - range = { method.start, method.finish }, + name = name, + skind = define.SymbolKind.Method, + ckind = define.CompletionItemKind.Method, + source = method, } end end @@ -55,16 +56,113 @@ local function searchFile(uri, key, results) end ---@async -return function (key) - local results = {} +---@param key string +---@param suri? uri +---@param results table[] +local function searchGlobalAndClass(key, suri, results) + for _, global in pairs(vm.getAllGlobals()) do + local name = global:getCodeName() + if matchKey(key, name) then + local sets + if suri then + sets = global:getSets(suri) + else + sets = global:getAllSets() + end + for _, set in ipairs(sets) do + local skind, ckind + if set.type == 'doc.class' then + skind = define.SymbolKind.Class + ckind = define.CompletionItemKind.Class + elseif set.type == 'doc.alias' then + skind = define.SymbolKind.Struct + ckind = define.CompletionItemKind.Struct + else + skind = define.SymbolKind.Variable + ckind = define.CompletionItemKind.Variable + end + results[#results+1] = { + name = name, + skind = skind, + ckind = ckind, + source = set, + } + end + await.delay() + end + end +end + +---@async +---@param key string +---@param suri? uri +---@param results table[] +local function searchClassField(key, suri, results) + local class, inField = key:match('^(.+)%.(.-)$') + if not class then + return + end + local global = vm.getGlobal('type', class) + if not global then + return + end + local set + if suri then + set = global:getSets(suri)[1] + else + set = global:getAllSets()[1] + end + if not set then + return + end + suri = suri or guide.getUri(set) + vm.getClassFields(suri, global, vm.ANY, function (field) + if field.type == 'generic' then + return + end + ---@cast field -vm.generic + local keyName = guide.getKeyName(field) + if not keyName then + return + end + if not matchKey(inField, keyName) then + return + end + results[#results+1] = { + name = class .. '.' .. keyName, + skind = define.SymbolKind.Field, + ckind = define.SymbolKind.Field, + source = field, + } + end) +end - for uri in files.eachFile() do +---@async +---@param key string +---@param suri? uri +---@param results table[] +local function searchWords(key, suri, results) + for uri in files.eachFile(suri) do searchFile(uri, key, results) if #results > 1000 then break end await.delay() end +end + +---@async +---@param key string +---@param suri? uri +---@param includeWords? boolean +return function (key, suri, includeWords) + local results = {} + + searchGlobalAndClass(key, suri, results) + searchClassField(key, suri, results) + if includeWords then + searchWords(key, suri, results) + end return results end diff --git a/script/doctor.lua b/script/doctor.lua index e1044689a..478831c36 100644 --- a/script/doctor.lua +++ b/script/doctor.lua @@ -20,6 +20,7 @@ local maxinterger = 10000 local mathType = math.type local _G = _G local registry = getregistry() +local ccreate = coroutine.create _ENV = nil @@ -161,6 +162,9 @@ local function private(o) end local m = private {} + +m._ignoreMainThread = true + --- ่Žทๅ–ๅ†…ๅญ˜ๅฟซ็…ง๏ผŒ็”Ÿๆˆไธ€ไธชๅ†…้ƒจๆ•ฐๆฎ็ป“ๆž„ใ€‚ --- ไธ€่ˆฌไธ็”จ่ฟ™ไธชAPI๏ผŒๆ”น็”จ report ๆˆ– catchใ€‚ ---@return table @@ -191,7 +195,6 @@ m.snapshot = private(function () local find local mark = private {} - local function findTable(t, result) result = result or {} local mt = getmetatable(t) @@ -211,21 +214,45 @@ m.snapshot = private(function () if not wk then local keyInfo = find(k) if keyInfo then - result[#result+1] = private { - type = 'key', - name = formatName(k), - info = keyInfo, - } + if wv then + find(v) + local valueResults = mark[v] + if valueResults then + valueResults[#valueResults+1] = private { + type = 'weakvalue-key', + name = formatName(t) .. '|' .. formatName(v), + info = keyInfo, + } + end + else + result[#result+1] = private { + type = 'key', + name = formatName(k), + info = keyInfo, + } + end end end if not wv then local valueInfo = find(v) if valueInfo then - result[#result+1] = private { - type = 'field', - name = formatName(k) .. '|' .. formatName(v), - info = valueInfo, - } + if wk then + find(k) + local keyResults = mark[k] + if keyResults then + keyResults[#keyResults+1] = private { + type = 'weakkey-field', + name = formatName(t) .. '|' .. formatName(k), + info = valueInfo, + } + end + else + result[#result+1] = private { + type = 'field', + name = formatName(k) .. '|' .. formatName(v), + info = valueInfo, + } + end end end end @@ -423,6 +450,20 @@ m.snapshot = private(function () info = find(_G), } end + for name, mt in next, private { + ['nil'] = getmetatable(nil), + ['boolean'] = getmetatable(true), + ['number'] = getmetatable(0), + ['string'] = getmetatable(''), + ['function'] = getmetatable(function () end), + ['thread'] = getmetatable(ccreate(function () end)), + } do + result.info[#result.info+1] = private { + type = 'metatable', + name = name, + info = find(mt), + } + end if m._cache then m._lastCache = result end diff --git a/script/encoder/ansi.lua b/script/encoder/ansi.lua index f5273c512..f89ddbc11 100644 --- a/script/encoder/ansi.lua +++ b/script/encoder/ansi.lua @@ -1,24 +1,24 @@ local platform = require 'bee.platform' -local unicode +local windows -if platform.OS == 'Windows' then - unicode = require 'bee.unicode' +if platform.os == 'windows' then + windows = require 'bee.windows' end local m = {} function m.toutf8(text) - if not unicode then + if not windows then return text end - return unicode.a2u(text) + return windows.a2u(text) end function m.fromutf8(text) - if not unicode then + if not windows then return text end - return unicode.u2a(text) + return windows.u2a(text) end return m diff --git a/script/file-uri.lua b/script/file-uri.lua index 3e916acf7..192f3ab5a 100644 --- a/script/file-uri.lua +++ b/script/file-uri.lua @@ -25,7 +25,7 @@ local m = {} ---@return uri uri function m.encode(path) local authority = '' - if platform.OS == 'Windows' then + if platform.os == 'windows' then path = path:gsub('\\', '/') end @@ -49,7 +49,7 @@ function m.encode(path) --lower-case windows drive letters in /C:/fff or C:/fff local start, finish, drive = path:find '/(%u):' - if drive then + if drive and finish then path = path:sub(1, start) .. drive:lower() .. path:sub(finish, -1) end @@ -82,7 +82,7 @@ function m.decode(uri) else value = path end - if platform.OS == 'Windows' then + if platform.os == 'windows' then value = value:gsub('/', '\\') end return value @@ -102,11 +102,14 @@ function m.isValid(uri) if path == '' then return false end + if scheme ~= 'file' then + return false + end return true end function m.normalize(uri) - if uri == '' then + if not m.isValid(uri) then return uri end return m.encode(m.decode(uri)) diff --git a/script/files.lua b/script/files.lua index 8333f9564..b7f2a924c 100644 --- a/script/files.lua +++ b/script/files.lua @@ -13,22 +13,29 @@ local smerger = require 'string-merger' local progress = require "progress" local encoder = require 'encoder' local scope = require 'workspace.scope' +local lazy = require 'lazytable' +local cacher = require 'lazy-cacher' +local sp = require 'bee.subprocess' +local pub = require 'pub' ---@class file ----@field uri uri ----@field content string ----@field _ref? integer ----@field trusted? boolean ----@field rows? integer[] ----@field originText? string ----@field text string ----@field version? integer ----@field originLines? integer[] ----@field state? parser.state ----@field _diffInfo? table[] ----@field cache table +---@field uri uri +---@field ref? integer +---@field trusted? boolean +---@field rows? integer[] +---@field originText? string +---@field text? string +---@field version? integer +---@field originLines? integer[] +---@field diffInfo? table[] +---@field cache? table +---@field id integer +---@field state? parser.state +---@field compileCount? integer +---@field words? table ---@class files +---@field lazyCache? lazy-cacher local m = {} m.watchList = {} @@ -43,17 +50,40 @@ function m.reset() m.visible = {} m.globalVersion = 0 m.fileCount = 0 - m.astCount = 0 - m.astMap = {} + ---@type table + m.stateMap = setmetatable({}, util.MODE_V) + ---@type table + m.stateTrace = setmetatable({}, util.MODE_K) end m.reset() +local fileID = util.counter() + local uriMap = {} + +---@param path fs.path +---@return fs.path +local function getRealParent(path) + local parent = path:parent_path() + if parent:string():gsub('^%w+:', string.lower) + == path :string():gsub('^%w+:', string.lower) then + return path + end + local res = fs.fullpath(path) + return getRealParent(parent) / res:filename() +end + -- ่Žทๅ–ๆ–‡ไปถ็š„็œŸๅฎžuri๏ผŒไฝ†ไธ็ฉฟ้€่ฝฏ้“พๆŽฅ ---@param uri uri ---@return uri function m.getRealUri(uri) + if platform.os ~= 'windows' then + return furi.normalize(uri) + end + if not furi.isValid(uri) then + return uri + end local filename = furi.decode(uri) -- normalize uri uri = furi.encode(filename) @@ -71,11 +101,16 @@ function m.getRealUri(uri) if uri == ruri then return ruri end - if not uriMap[ruri] then - uriMap[ruri] = uri + local real = getRealParent(path:parent_path()) / res:filename() + ruri = furi.encode(real:string()) + if uri == ruri then + return ruri + end + if not uriMap[uri] then + uriMap[uri] = true log.warn(('Fix real file uri: %s -> %s'):format(uri, ruri)) end - return uriMap[ruri] + return ruri end --- ๆ‰“ๅผ€ๆ–‡ไปถ @@ -97,7 +132,7 @@ function m.close(uri) end m.onWatch('close', uri) if file then - if (file._ref or 0) <= 0 and not m.isOpen(uri) then + if (file.ref or 0) <= 0 and not m.isOpen(uri) then m.remove(uri) end end @@ -156,7 +191,7 @@ end ---@return string local function pluginOnSetText(file, text) local plugin = require 'plugin' - file._diffInfo = nil + file.diffInfo = nil local suc, result = plugin.dispatch('OnSetText', file.uri, text) if not suc then if DEVELOP and result then @@ -170,7 +205,7 @@ local function pluginOnSetText(file, text) local diffs suc, result, diffs = xpcall(smerger.mergeDiff, log.error, text, result) if suc then - file._diffInfo = diffs + file.diffInfo = diffs file.originLines = parser.lines(text) return result else @@ -182,6 +217,12 @@ local function pluginOnSetText(file, text) return text end +---@param file file +function m.removeState(file) + file.state = nil + m.stateMap[file.uri] = nil +end + --- ่ฎพ็ฝฎๆ–‡ไปถๆ–‡ๆœฌ ---@param uri uri ---@param text? string @@ -201,6 +242,7 @@ function m.setText(uri, text, isTrust, callback) if not m.fileMap[uri] then m.fileMap[uri] = { uri = uri, + id = fileID(), } m.fileCount = m.fileCount + 1 create = true @@ -220,15 +262,16 @@ function m.setText(uri, text, isTrust, callback) if file.originText == text then return end + local clock = os.clock() local newText = pluginOnSetText(file, text) - file.text = newText - file.trusted = isTrust - file.originText = text - file.rows = nil - file.words = nil - m.astMap[uri] = nil - file.cache = {} - file.cacheActiveTime = math.huge + m.removeState(file) + file.text = newText + file.trusted = isTrust + file.originText = text + file.rows = nil + file.words = nil + file.compileCount = 0 + file.cache = {} m.globalVersion = m.globalVersion + 1 m.onWatch('version', uri) if create then @@ -242,6 +285,7 @@ function m.setText(uri, text, isTrust, callback) util.saveFile(LOGPATH .. '/diffed.lua', newText) end end + log.trace('Set text:', uri, 'takes', os.clock() - clock, 'sec.') --if instance or TEST then --else @@ -258,6 +302,9 @@ end function m.resetText(uri) local file = m.fileMap[uri] + if not file then + return + end local originText = file.originText file.originText = nil m.setText(uri, originText, file.trusted) @@ -270,7 +317,7 @@ function m.setRawText(uri, text) local file = m.fileMap[uri] file.text = text file.originText = text - m.astMap[uri] = nil + m.removeState(file) end function m.getCachedRows(uri) @@ -360,6 +407,16 @@ function m.getOriginText(uri) return file.originText end +---@param uri uri +---@param text string +function m.setOriginText(uri, text) + local file = m.fileMap[uri] + if not file then + return + end + file.originText = text +end + --- ่Žทๅ–ๆ–‡ไปถๅŽŸๅง‹่กŒ่กจ ---@param uri uri ---@return integer[] @@ -387,8 +444,8 @@ function m.addRef(uri) if not file then return nil end - file._ref = (file._ref or 0) + 1 - log.debug('add ref', uri) + file.ref = (file.ref or 0) + 1 + log.debug('add ref', uri, file.ref) return function () m.delRef(uri) end @@ -399,9 +456,9 @@ function m.delRef(uri) if not file then return end - file._ref = (file._ref or 0) - 1 - log.debug('del ref', uri) - if file._ref <= 0 and not m.isOpen(uri) then + file.ref = (file.ref or 0) - 1 + log.debug('del ref', uri, file.ref) + if file.ref <= 0 and not m.isOpen(uri) then m.remove(uri) end end @@ -413,8 +470,8 @@ function m.remove(uri) if not file then return end + m.removeState(file) m.fileMap[uri] = nil - m.astMap[uri] = nil m._pairsCache = nil m.fileCount = m.fileCount - 1 @@ -439,6 +496,7 @@ function m.getAllUris(suri) files[i] = uri end end + table.sort(files) return files end @@ -470,12 +528,68 @@ function m.eachDll() return pairs(map) end -function m.compileState(uri, text) +function m.getLazyCache() + if not m.lazyCache then + local cachePath = string.format('%s/cache/%d' + , LOGPATH + , sp.get_id() + ) + m.lazyCache = cacher(cachePath, log.error) + end + return m.lazyCache +end + +---@param state parser.state +---@param file file +function m.compileStateThen(state, file) + m.stateTrace[state] = true + m.stateMap[file.uri] = state + state.uri = file.uri + state.lua = file.text + state.ast.uri = file.uri + state.diffInfo = file.diffInfo + state.originLines = file.originLines + state.originText = file.originText + + local clock = os.clock() + parser.luadoc(state) + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('Parse LuaDoc of [%s] takes [%.3f] sec, size [%.3f] kb.'):format(file.uri, passed, #file.text / 1000)) + end + + if LAZY and not file.trusted then + local cache = m.getLazyCache() + local id = ('%d'):format(file.id) + clock = os.clock() + state = lazy.build(state, cache:writterAndReader(id)):entry() + passed = os.clock() - clock + if passed > 0.1 then + log.warn(('Convert lazy-table for [%s] takes [%.3f] sec, size [%.3f] kb.'):format(file.uri, passed, #file.text / 1000)) + end + end + + file.compileCount = file.compileCount + 1 + if file.compileCount >= 3 then + file.state = state + log.debug('State persistence:', file.uri) + end + + m.onWatch('compile', file.uri) +end + +---@param uri uri +---@return boolean +function m.checkPreload(uri) + local file = m.fileMap[uri] + if not file then + return false + end local ws = require 'workspace' local client = require 'client' if not m.isOpen(uri) and not m.isLibrary(uri) - and #text >= config.get(uri, 'Lua.workspace.preloadFileSize') * 1000 then + and #file.text >= config.get(uri, 'Lua.workspace.preloadFileSize') * 1000 then if not m.notifyCache['preloadFileSize'] then m.notifyCache['preloadFileSize'] = {} m.notifyCache['skipLargeFileCount'] = 0 @@ -486,7 +600,7 @@ function m.compileState(uri, text) local message = lang.script('WORKSPACE_SKIP_LARGE_FILE' , ws.getRelativePath(uri) , config.get(uri, 'Lua.workspace.preloadFileSize') - , #text / 1000 + , #file.text / 1000 ) if m.notifyCache['skipLargeFileCount'] <= 1 then client.showMessage('Info', message) @@ -494,12 +608,95 @@ function m.compileState(uri, text) client.logMessage('Info', message) end end - return nil + return false + end + return true +end + +---@param uri uri +---@param callback fun(state: parser.state?) +function m.compileStateAsync(uri, callback) + local file = m.fileMap[uri] + if not file then + callback(nil) + return + end + if m.stateMap[uri] then + callback(m.stateMap[uri]) + return + end + + ---@type brave.param.compile.options + local options = { + special = config.get(uri, 'Lua.runtime.special'), + unicodeName = config.get(uri, 'Lua.runtime.unicodeName'), + nonstandardSymbol = util.arrayToHash(config.get(uri, 'Lua.runtime.nonstandardSymbol')), + } + + ---@type brave.param.compile + local params = { + uri = uri, + text = file.text, + mode = 'Lua', + version = config.get(uri, 'Lua.runtime.version'), + options = options + } + pub.task('compile', params, function (result) + if file.text ~= params.text then + return + end + if not result.state then + log.error('Compile failed:', uri, result.err) + callback(nil) + return + end + m.compileStateThen(result.state, file) + callback(result.state) + end) +end + +local function pluginOnTransformAst(uri, state) + local plugin = require 'plugin' + ---TODO: maybe deepcopy astNode + local suc, result = plugin.dispatch('OnTransformAst', uri, state.ast) + if not suc then + return state + end + state.ast = result or state.ast + return state +end + +---@param uri uri +---@return parser.state? +function m.compileState(uri) + local file = m.fileMap[uri] + if not file then + return + end + if m.stateMap[uri] then + return m.stateMap[uri] + end + if not m.checkPreload(uri) then + return + end + + ---@type brave.param.compile.options + local options = { + special = config.get(uri, 'Lua.runtime.special'), + unicodeName = config.get(uri, 'Lua.runtime.unicodeName'), + nonstandardSymbol = util.arrayToHash(config.get(uri, 'Lua.runtime.nonstandardSymbol')), + } + + local ws = require 'workspace' + local client = require 'client' + if not client.isReady() then + log.error('Client not ready!', uri) end local prog = progress.create(uri, lang.script.WINDOW_COMPILING, 0.5) prog:setMessage(ws.getRelativePath(uri)) + log.trace('Compile State:', uri) local clock = os.clock() - local state, err = parser.compile(text + local state, err = parser.compile(file.text , 'Lua' , config.get(uri, 'Lua.runtime.version') , { @@ -511,60 +708,47 @@ function m.compileState(uri, text) ) local passed = os.clock() - clock if passed > 0.1 then - log.warn(('Compile [%s] takes [%.3f] sec, size [%.3f] kb.'):format(uri, passed, #text / 1000)) - end - --await.delay() - if state then - state.uri = uri - state.lua = text - state.ast.uri = uri - local clock = os.clock() - parser.luadoc(state) - local passed = os.clock() - clock - if passed > 0.1 then - log.warn(('Parse LuaDoc of [%s] takes [%.3f] sec, size [%.3f] kb.'):format(uri, passed, #text / 1000)) - end - m.astCount = m.astCount + 1 - local removed - setmetatable(state, {__gc = function () - if removed then - return - end - removed = true - m.astCount = m.astCount - 1 - end}) - return state - else + log.warn(('Compile [%s] takes [%.3f] sec, size [%.3f] kb.'):format(uri, passed, #file.text / 1000)) + end + + if not state then log.error('Compile failed:', uri, err) return nil end + + state = pluginOnTransformAst(uri, state) + if not state then + log.error('pluginOnTransformAst failed! discard the file state') + return nil + end + + m.compileStateThen(state, file) + + return state end +---@class parser.state +---@field diffInfo? table[] +---@field originLines? integer[] +---@field originText? string +---@field lua? string + --- ่Žทๅ–ๆ–‡ไปถ่ฏญๆณ•ๆ ‘ ---@param uri uri ----@return table? state +---@return parser.state? state function m.getState(uri) local file = m.fileMap[uri] if not file then return nil end - local state = m.astMap[uri] - if not state then - state = m.compileState(uri, file.text) - m.astMap[uri] = state - file.state = state - --await.delay() - end - file.cacheActiveTime = timer.clock() + local state = m.compileState(uri) return state end +---@param uri uri +---@return parser.state? function m.getLastState(uri) - local file = m.fileMap[uri] - if not file then - return nil - end - return file.state + return m.stateMap[uri] end function m.getFile(uri) @@ -584,43 +768,32 @@ local function isNameChar(text) end --- ๅฐ†ๅบ”็”จๅทฎๅผ‚ๅ‰็š„offset่ฝฌๆขไธบๅบ”็”จๅทฎๅผ‚ๅŽ็š„offset ----@param uri uri +---@param state parser.state ---@param offset integer ---@return integer start ---@return integer finish -function m.diffedOffset(uri, offset) - local file = m.getFile(uri) - if not file then - return offset, offset - end - if not file._diffInfo then +function m.diffedOffset(state, offset) + if not state.diffInfo then return offset, offset end - return smerger.getOffset(file._diffInfo, offset) + return smerger.getOffset(state.diffInfo, offset) end --- ๅฐ†ๅบ”็”จๅทฎๅผ‚ๅŽ็š„offset่ฝฌๆขไธบๅบ”็”จๅทฎๅผ‚ๅ‰็š„offset ----@param uri uri +---@param state parser.state ---@param offset integer ---@return integer start ---@return integer finish -function m.diffedOffsetBack(uri, offset) - local file = m.getFile(uri) - if not file then - return offset, offset - end - if not file._diffInfo then +function m.diffedOffsetBack(state, offset) + if not state.diffInfo then return offset, offset end - return smerger.getOffsetBack(file._diffInfo, offset) + return smerger.getOffsetBack(state.diffInfo, offset) end -function m.hasDiffed(uri) - local file = m.getFile(uri) - if not file then - return false - end - return file._diffInfo ~= nil +---@param state parser.state +function m.hasDiffed(state) + return state.diffInfo ~= nil end --- ่Žทๅ–ๆ–‡ไปถ็š„่‡ชๅฎšไน‰็ผ“ๅญ˜ไฟกๆฏ๏ผˆๅœจๆ–‡ไปถๅ†…ๅฎนๆ›ดๆ–ฐๅŽ่‡ชๅŠจๅคฑๆ•ˆ๏ผ‰ @@ -629,7 +802,6 @@ function m.getCache(uri) if not file then return nil end - --file.cacheActiveTime = timer.clock() return file.cache end @@ -666,7 +838,7 @@ function m.isDll(uri) if not ext then return false end - if platform.OS == 'Windows' then + if platform.os == 'windows' then if ext == 'dll' then return true end @@ -732,6 +904,50 @@ function m.getDllWords(uri) return file.words end +---@return integer +function m.countStates() + local n = 0 + for _ in pairs(m.stateTrace) do + n = n + 1 + end + return n +end + +---@param path string +---@return string +function m.normalize(path) + path = path:gsub('%$%{(.-)%}', function (key) + if key == '3rd' then + return (ROOT / 'meta' / '3rd'):string() + end + if key:sub(1, 4) == 'env:' then + local env = os.getenv(key:sub(5)) + return env + end + end) + path = util.expandPath(path) + path = path:gsub('^%.[/\\]+', '') + for _ = 1, 1000 do + if path:sub(1, 2) == '..' then + break + end + local count + path, count = path:gsub('[^/\\]+[/\\]+%.%.[/\\]', '/', 1) + if count == 0 then + break + end + end + if platform.os == 'windows' then + path = path:gsub('[/\\]+', '\\') + :gsub('[/\\]+$', '') + :gsub('^(%a:)$', '%1\\') + else + path = path:gsub('[/\\]+', '/') + :gsub('[/\\]+$', '') + end + return path +end + --- ๆณจๅ†Œไบ‹ไปถ ---@param callback async fun(ev: string, uri: uri) function m.watch(callback) @@ -746,23 +962,4 @@ function m.onWatch(ev, uri) end end -function m.init() - --TODO ๅฏไปฅๆธ…็ฉบๆ–‡ไปถ็ผ“ๅญ˜๏ผŒไน‹ๅŽ็œ‹่ฆไธ่ฆๅฏ็”จๅง - --timer.loop(10, function () - -- local list = {} - -- for _, file in pairs(m.fileMap) do - -- if timer.clock() - file.cacheActiveTime > 10.0 then - -- file.cacheActiveTime = math.huge - -- file.ast = nil - -- file.cache = {} - -- list[#list+1] = file.uri - -- end - -- end - -- if #list > 0 then - -- log.info('Flush file caches:', #list, '\n', table.concat(list, '\n')) - -- collectgarbage() - -- end - --end) -end - return m diff --git a/script/filewatch.lua b/script/filewatch.lua index ecc722557..d4850ca10 100644 --- a/script/filewatch.lua +++ b/script/filewatch.lua @@ -1,6 +1,8 @@ local fw = require 'bee.filewatch' local fs = require 'bee.filesystem' +local plat = require 'bee.platform' local await = require 'await' +local files = require 'files' local MODIFY = 1 << 0 local RENAME = 1 << 1 @@ -11,8 +13,15 @@ local function isExists(filename) if not suc or not exists then return false end - local suc, res = pcall(fs.canonical, path) - if not suc or res:string() ~= path:string() then + if plat.OS ~= 'Windows' then + return true + end + local res = fs.fullpath(path) + if not res then + return false + end + if res :string():gsub('^%w+:', string.lower) + ~= path:string():gsub('^%w+:', string.lower) then return false end return true @@ -24,13 +33,28 @@ local m = {} m._eventList = {} m._watchings = {} -function m.watch(path) +---@async +---@param path string +---@param recursive boolean +---@param filter? fun(path: string):boolean +function m.watch(path, recursive, filter) + if path == '' or not fs.is_directory(fs.path(path)) then + return function () end + end if m._watchings[path] then m._watchings[path].count = m._watchings[path].count + 1 else + local watch = fw.create() + if recursive then + watch:set_recursive(true) + watch:set_follow_symlinks(true) + watch:set_filter(filter) + end + log.debug('Watch add:', path) + watch:add(path) m._watchings[path] = { count = 1, - id = fw.add(path), + watch = watch, } log.debug('fw.add', path) end @@ -42,14 +66,13 @@ function m.watch(path) removed = true m._watchings[path].count = m._watchings[path].count - 1 if m._watchings[path].count == 0 then - fw.remove(m._watchings[path].id) m._watchings[path] = nil log.debug('fw.remove', path) end end end ----@param callback async fun() +---@param callback async fun(ev: string, path: string) function m.event(callback) m._eventList[#m._eventList+1] = callback end @@ -64,19 +87,23 @@ end function m.update() local collect - for _ = 1, 10000 do - local ev, path = fw.select() - if not ev then - break - end - log.debug('filewatch:', ev, path) - if not collect then - collect = {} - end - if ev == 'modify' then - collect[path] = (collect[path] or 0) | MODIFY - elseif ev == 'rename' then - collect[path] = (collect[path] or 0) | RENAME + for _, watching in pairs(m._watchings) do + local watch = watching.watch + for _ = 1, 10000 do + local ev, path = watch:select() + if not ev then + break + end + path = files.normalize(path) + log.debug('filewatch:', ev, path) + if not collect then + collect = {} + end + if ev == 'modify' then + collect[path] = (collect[path] or 0) | MODIFY + elseif ev == 'rename' then + collect[path] = (collect[path] or 0) | RENAME + end end end diff --git a/script/fs-utility.lua b/script/fs-utility.lua index b789177c8..35b336fe6 100644 --- a/script/fs-utility.lua +++ b/script/fs-utility.lua @@ -96,11 +96,15 @@ local function split(str, sep) return t end +---@class dummyfs +---@operator div(string|fs.path|dummyfs): dummyfs +---@field files table local dfs = {} dfs.__index = dfs dfs.type = 'dummy' dfs.path = '' +---@return dummyfs function m.dummyFS(t) return setmetatable({ files = t or {}, @@ -124,6 +128,7 @@ function dfs:__div(filename) return new end +---@package function dfs:_open(index) local paths = split(self.path, '[/\\]') local current = self.files @@ -143,6 +148,7 @@ function dfs:_open(index) return current end +---@package function dfs:_filename() return self.path:match '[^/\\]+$' end @@ -167,6 +173,7 @@ function dfs:string() return self.path end +---@return fun(): dummyfs? function dfs:listDirectory() local dir = self:_open() if type(dir) ~= 'table' then @@ -254,11 +261,12 @@ function dfs:saveFile(path, text) return false, 'ๆ— ๆณ•ๆ‰“ๅผ€:' .. path end dir[filename] = text + return true end ----@param path string|fs.path +---@param path string|fs.path|dummyfs ---@param option table ----@return fs.path? +---@return fs.path|dummyfs? local function fsAbsolute(path, option) if type(path) == 'string' then local suc, res = pcall(fs.path, path) @@ -285,10 +293,14 @@ local function fsIsDirectory(path, option) if path.type == 'dummy' then return path:isDirectory() end + ---@cast path -dummyfs local status = fs.symlink_status(path):type() return status == 'directory' end +---@param path fs.path|dummyfs|nil +---@param option table +---@return fun(): fs.path|dummyfs|nil local function fsPairs(path, option) if not path then return function () end @@ -338,6 +350,7 @@ local function fsSave(path, text, option) return false end if path.type == 'dummy' then + ---@cast path -fs.path local dir = path:_open(-2) if not dir then option.err[#option.err+1] = 'ๆ— ๆณ•ๆ‰“ๅผ€:' .. path:string() @@ -376,6 +389,7 @@ local function fsLoad(path, option) return nil end else + ---@cast path -dummyfs local text, err = m.loadFile(path) if text then return text @@ -398,6 +412,7 @@ local function fsCopy(source, target, option) end return fsSave(target, sourceText, option) else + ---@cast source -dummyfs if target.type == 'dummy' then local sourceText, err = m.loadFile(source) if not sourceText then @@ -416,6 +431,8 @@ local function fsCopy(source, target, option) return true end +---@param path dummyfs|fs.path +---@param option table local function fsCreateDirectories(path, option) if not path then return @@ -439,7 +456,7 @@ local function fileRemove(path, option) return end if fsIsDirectory(path, option) then - for child in fsPairs(path) do + for child in fsPairs(path, option) do fileRemove(child, option) end end @@ -448,8 +465,8 @@ local function fileRemove(path, option) end end ----@param source fs.path? ----@param target fs.path? +---@param source fs.path|dummyfs? +---@param target fs.path|dummyfs? ---@param option table local function fileCopy(source, target, option) if not source or not target then @@ -460,7 +477,7 @@ local function fileCopy(source, target, option) local isExists = fsExists(target, option) if isDir1 then if isDir2 or fsCreateDirectories(target, option) then - for filePath in fsPairs(source) do + for filePath in fsPairs(source, option) do local name = filePath:filename():string() fileCopy(filePath, target / name, option) end @@ -484,8 +501,8 @@ local function fileCopy(source, target, option) end end ----@param source fs.path? ----@param target fs.path? +---@param source fs.path|dummyfs? +---@param target fs.path|dummyfs? ---@param option table local function fileSync(source, target, option) if not source or not target then @@ -497,10 +514,18 @@ local function fileSync(source, target, option) if isDir1 then if isDir2 then local fileList = m.fileList() - for filePath in fs.pairs(target) do - fileList[filePath] = true + if type(target) == 'table' then + ---@cast target dummyfs + for filePath in target:listDirectory() do + fileList[filePath] = true + end + else + ---@cast target fs.path + for filePath in fs.pairs(target) do + fileList[filePath] = true + end end - for filePath in fsPairs(source) do + for filePath in fsPairs(source, option) do local name = filePath:filename():string() local targetPath = target / name fileSync(filePath, targetPath, option) @@ -513,8 +538,8 @@ local function fileSync(source, target, option) if isExists then fileRemove(target, option) end - if fsCreateDirectories(target) then - for filePath in fsPairs(source) do + if fsCreateDirectories(target, option) then + for filePath in fsPairs(source, option) do local name = filePath:filename():string() fileCopy(filePath, target / name, option) end @@ -545,7 +570,6 @@ end --- ๆ–‡ไปถๅˆ—่กจ function m.fileList(option) option = option or buildOption(option) - local os = platform.OS local keyMap = {} local fileList = {} local function computeKey(path) @@ -593,8 +617,8 @@ function m.fileRemove(path, option) end --- ๅคๅˆถๆ–‡ไปถ๏ผˆๅคน๏ผ‰ ----@param source string|fs.path ----@param target string|fs.path +---@param source string|fs.path|dummyfs +---@param target string|fs.path|dummyfs ---@return table function m.fileCopy(source, target, option) option = buildOption(option) @@ -607,8 +631,8 @@ function m.fileCopy(source, target, option) end --- ๅŒๆญฅๆ–‡ไปถ๏ผˆๅคน๏ผ‰ ----@param source string|fs.path ----@param target string|fs.path +---@param source string|fs.path|dummyfs +---@param target string|fs.path|dummyfs ---@return table function m.fileSync(source, target, option) option = buildOption(option) @@ -620,6 +644,8 @@ function m.fileSync(source, target, option) return option end +---@param dir fs.path +---@param callback fun(fullPath: fs.path) function m.scanDirectory(dir, callback) for fullpath in fs.pairs(dir) do local status = fs.symlink_status(fullpath):type() diff --git a/script/gc.lua b/script/gc.lua index 7bb81569c..927395851 100644 --- a/script/gc.lua +++ b/script/gc.lua @@ -1,12 +1,13 @@ local util = require 'utility' ---@class gc ----@field _list table +---@field package _list table local mt = {} mt.__index = mt mt.type = 'gc' mt._removed = false +---@package mt._max = 10 local function destroyGCObject(obj) @@ -27,7 +28,9 @@ local function isRemoved(obj) for i = 1, 1000 do local n, v = debug.getupvalue(obj, i) if not n then - log.warn('ๅ‡ฝๆ•ฐๅผๆžๆž„ๅ™จๆฒกๆœ‰ removed ไธŠๅ€ผ๏ผ', util.dump(debug.getinfo(obj))) + if i > 1 then + log.warn('ๅ‡ฝๆ•ฐๅผๆžๆž„ๅ™จๆฒกๆœ‰ removed ไธŠๅ€ผ๏ผ', util.dump(debug.getinfo(obj))) + end break end if n == 'removed' then diff --git a/script/glob/gitignore.lua b/script/glob/gitignore.lua index de8fd005c..fef0a0d74 100644 --- a/script/glob/gitignore.lua +++ b/script/glob/gitignore.lua @@ -49,6 +49,7 @@ local parser = m.P { ---@field errors table[] ---@field matcher table ---@field interface function[] +---@field data table local mt = {} mt.__index = mt mt.__name = 'gitignore' @@ -90,9 +91,9 @@ function mt:setInterface(key, func) self.interface[key] = func end -function mt:callInterface(name, ...) +function mt:callInterface(name, params) local func = self.interface[name] - return func(...) + return func(params, self.data) end function mt:hasInterface(name) @@ -223,6 +224,7 @@ return function (pattern, options, interface) matcher = {}, errors = {}, interface = {}, + data = {}, }, mt) if type(options) == 'table' then diff --git a/script/global.d.lua b/script/global.d.lua index 56f3019f8..ead46ca9a 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -22,6 +22,10 @@ CONFIGPATH = '' ---@type boolean SHOWSOURCE = false +---display the internal semantic of the hovring token, use command line: --shownode=true +---@type boolean +SHOWNODE = false + ---trace every searching into log, use command line: --trace=true ---@type boolean TRACE = false @@ -44,8 +48,53 @@ PREVIEW = false ---@type string CHECK = '' +--make docs path +---@type string +DOC = '' + +--output directory path for documentation (doc.json, ...) +---@type string +DOC_OUT_PATH = '' + +---update an existing doc.json +---@type string +DOC_UPDATE = '' + ---@type string | '"Error"' | '"Warning"' | '"Information"' | '"Hint"' CHECKLEVEL = 'Warning' +--Where to write the check results (JSON). +-- +--If nil, use `LOGPATH/check.json`. +---@type string|nil +CHECK_OUT_PATH = '' + ---@type 'trace' | 'debug' | 'info' | 'warn' | 'error' LOGLEVEL = 'warn' + +-- (experiment) Cache data into disk, may reduce memory usage, but increase CPU usage. +---@type boolean +LAZY = false + +-- (experiment) Improve performance, but reduce accuracy +---@type boolean +CACHEALIVE = false + +-- (experiment) Compile files in multi cpu cores +---@type integer +COMPILECORES = 0 + +-- TODO: delete this after new config +---@diagnostic disable-next-line: lowercase-global +jit = false + +-- connect to client by socket +---@type integer +SOCKET = 0 + +-- Allowing the use of the root directory or home directory as the workspace +FORCE_ACCEPT_WORKSPACE = false + +-- Trust all plugins that are being loaded by workspace config files. +-- This is potentially unsafe for normal use and meant for usage in CI environments only. +TRUST_ALL_PLUGINS = false diff --git a/script/json-beautify.lua b/script/json-beautify.lua index ede0e75b0..f3467f078 100644 --- a/script/json-beautify.lua +++ b/script/json-beautify.lua @@ -5,23 +5,36 @@ local error = error local table_concat = table.concat local table_sort = table.sort local string_rep = string.rep -local math_type = math.type local setmetatable = setmetatable -local getmetatable = getmetatable -local statusMark -local statusQue +local math_type + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then + local math_floor = math.floor + function math_type(v) + if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then + return "integer" + end + return "float" + end +else + math_type = math.type +end + +local statusVisited +local statusBuilder local statusDep local statusOpt local defaultOpt = { newline = "\n", - indent = " ", + indent = " ", + depth = 0, } defaultOpt.__index = defaultOpt local function encode_newline() - statusQue[#statusQue+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) + statusBuilder[#statusBuilder+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) end local encode_map = {} @@ -32,96 +45,132 @@ end local function encode(v) local res = encode_map[type(v)](v) - statusQue[#statusQue+1] = res + statusBuilder[#statusBuilder+1] = res end function encode_map.string(v) - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(v) + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(v) return '"' end function encode_map.table(t) local first_val = next(t) if first_val == nil then - if getmetatable(t) == json.object then + if json.isObject(t) then return "{}" else return "[]" end end - if statusMark[t] then + if statusVisited[t] then error("circular reference") end - statusMark[t] = true + statusVisited[t] = true if type(first_val) == 'string' then local key = {} for k in next, t do if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end key[#key+1] = k end table_sort(key) - statusQue[#statusQue+1] = "{" + statusBuilder[#statusBuilder+1] = "{" statusDep = statusDep + 1 encode_newline() - local k = key[1] - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(k) - statusQue[#statusQue+1] = '": ' - encode(t[k]) + do + local k = key[1] + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(k) + statusBuilder[#statusBuilder+1] = '": ' + encode(t[k]) + end for i = 2, #key do local k = key[i] - statusQue[#statusQue+1] = "," + statusBuilder[#statusBuilder+1] = "," encode_newline() - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(k) - statusQue[#statusQue+1] = '": ' + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(k) + statusBuilder[#statusBuilder+1] = '": ' encode(t[k]) end statusDep = statusDep - 1 encode_newline() - statusMark[t] = nil + statusVisited[t] = nil return "}" - else + elseif json.supportSparseArray then local max = 0 for k in next, t do if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end if max < k then max = k end end - statusQue[#statusQue+1] = "[" + statusBuilder[#statusBuilder+1] = "[" statusDep = statusDep + 1 encode_newline() encode(t[1]) for i = 2, max do - statusQue[#statusQue+1] = "," + statusBuilder[#statusBuilder+1] = "," encode_newline() encode(t[i]) end statusDep = statusDep - 1 encode_newline() - statusMark[t] = nil + statusVisited[t] = nil + return "]" + else + if t[1] == nil then + error("invalid table: sparse array is not supported") + end + statusBuilder[#statusBuilder+1] = "[" + statusDep = statusDep + 1 + encode_newline() + encode(t[1]) + local count = 2 + while t[count] ~= nil do + statusBuilder[#statusBuilder+1] = "," + encode_newline() + encode(t[count]) + count = count + 1 + end + if next(t, count-1) ~= nil then + local k = next(t, count-1) + if type(k) == "number" then + error("invalid table: sparse array is not supported") + else + error("invalid table: mixed or invalid key types: "..k) + end + end + statusDep = statusDep - 1 + encode_newline() + statusVisited[t] = nil return "]" end end -local function beautify(v, option) - if type(v) == "string" then - v = json.decode(v) - end - statusMark = {} - statusQue = {} - statusDep = 0 - statusOpt = option and setmetatable(option, defaultOpt) or defaultOpt +local function beautify_option(option) + return setmetatable(option or {}, defaultOpt) +end + +local function beautify_builder(builder, v, option) + statusVisited = {} + statusBuilder = builder + statusOpt = beautify_option(option) + statusDep = statusOpt.depth encode(v) - return table_concat(statusQue) +end + +local function beautify(v, option) + beautify_builder({}, v, option) + return table_concat(statusBuilder) end json.beautify = beautify +json._beautify_builder = beautify_builder +json._beautify_option = beautify_option return json diff --git a/script/json-edit.lua b/script/json-edit.lua new file mode 100644 index 000000000..efa1216fa --- /dev/null +++ b/script/json-edit.lua @@ -0,0 +1,725 @@ +local type = type +local next = next +local error = error +local tonumber = tonumber +local table_concat = table.concat +local table_move = table.move +local string_char = string.char +local string_byte = string.byte +local string_find = string.find +local string_match = string.match +local string_gsub = string.gsub +local string_sub = string.sub +local string_rep = string.rep +local string_format = string.format + +local utf8_char +local math_type + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then + local math_floor = math.floor + function utf8_char(c) + if c <= 0x7f then + return string_char(c) + elseif c <= 0x7ff then + return string_char(math_floor(c / 64) + 192, c % 64 + 128) + elseif c <= 0xffff then + return string_char( + math_floor(c / 4096) + 224, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + elseif c <= 0x10ffff then + return string_char( + math_floor(c / 262144) + 240, + math_floor(c % 262144 / 4096) + 128, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + end + error(string_format("invalid UTF-8 code '%x'", c)) + end + function math_type(v) + if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then + return "integer" + end + return "float" + end + function table_move(a1, f, e, t, a2) + for i = f, e do + a2[t+(i-f)] = a1[i] + end + return a2 + end +else + utf8_char = utf8.char + math_type = math.type +end + +local json = require "json-beautify" + +local encode_escape_map = { + [ "\"" ] = "\\\"", + [ "\\" ] = "\\\\", + [ "/" ] = "\\/", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local decode_escape_set = {} +local decode_escape_map = {} +for k, v in next, encode_escape_map do + decode_escape_map[v] = k + decode_escape_set[string_byte(v, 2)] = true +end + +local statusBuf +local statusPos +local statusTop +local statusAry = {} +local statusRef = {} +local statusAst = {} + +local function find_line() + local line = 1 + local pos = 1 + while true do + local f, _, nl1, nl2 = string_find(statusBuf, '([\n\r])([\n\r]?)', pos) + if not f then + return line, statusPos - pos + 1 + end + local newpos = f + ((nl1 == nl2 or nl2 == '') and 1 or 2) + if newpos > statusPos then + return line, statusPos - pos + 1 + end + pos = newpos + line = line + 1 + end +end + +local function decode_error(msg) + error(string_format("ERROR: %s at line %d col %d", msg, find_line()), 2) +end + +local function get_word() + return string_match(statusBuf, "^[^ \t\r\n%]},]*", statusPos) +end + +local function skip_comment(b) + if b ~= 47 --[[ '/' ]] then + return + end + local c = string_byte(statusBuf, statusPos+1) + if c == 42 --[[ '*' ]] then + -- block comment + local pos = string_find(statusBuf, "*/", statusPos) + if pos then + statusPos = pos + 2 + else + statusPos = #statusBuf + 1 + end + return true + elseif c == 47 --[[ '/' ]] then + -- line comment + local pos = string_find(statusBuf, "[\r\n]", statusPos) + if pos then + statusPos = pos + else + statusPos = #statusBuf + 1 + end + return true + end +end + +local function next_byte() + local pos = string_find(statusBuf, "[^ \t\r\n]", statusPos) + if pos then + statusPos = pos + local b = string_byte(statusBuf, pos) + if not skip_comment(b) then + return b + end + return next_byte() + end + return -1 +end + +local function decode_unicode_surrogate(s1, s2) + return utf8_char(0x10000 + (tonumber(s1, 16) - 0xd800) * 0x400 + (tonumber(s2, 16) - 0xdc00)) +end + +local function decode_unicode_escape(s) + return utf8_char(tonumber(s, 16)) +end + +local function decode_string() + local has_unicode_escape = false + local has_escape = false + local i = statusPos + 1 + while true do + i = string_find(statusBuf, '[%z\1-\31\\"]', i) + if not i then + decode_error "expected closing quote for string" + end + local x = string_byte(statusBuf, i) + if x < 32 then + statusPos = i + decode_error "control character in string" + end + if x == 34 --[[ '"' ]] then + local s = string_sub(statusBuf, statusPos + 1, i - 1) + if has_unicode_escape then + s = string_gsub(string_gsub(s + , "\\u([dD][89aAbB]%x%x)\\u([dD][c-fC-F]%x%x)", decode_unicode_surrogate) + , "\\u(%x%x%x%x)", decode_unicode_escape) + end + if has_escape then + s = string_gsub(s, "\\.", decode_escape_map) + end + statusPos = i + 1 + return s + end + --assert(x == 92 --[[ "\\" ]]) + local nx = string_byte(statusBuf, i+1) + if nx == 117 --[[ "u" ]] then + if not string_match(statusBuf, "^%x%x%x%x", i+2) then + statusPos = i + decode_error "invalid unicode escape in string" + end + has_unicode_escape = true + i = i + 6 + else + if not decode_escape_set[nx] then + statusPos = i + decode_error("invalid escape char '" .. (nx and string_char(nx) or "") .. "' in string") + end + has_escape = true + i = i + 2 + end + end +end + +local function decode_number() + local num, c = string_match(statusBuf, '^([0-9]+%.?[0-9]*)([eE]?)', statusPos) + if not num or string_byte(num, -1) == 0x2E --[[ "." ]] then + decode_error("invalid number '" .. get_word() .. "'") + end + if c ~= '' then + num = string_match(statusBuf, '^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]', statusPos) + if not num then + decode_error("invalid number '" .. get_word() .. "'") + end + end + statusPos = statusPos + #num + return tonumber(num) +end + +local function decode_number_zero() + local num, c = string_match(statusBuf, '^(.%.?[0-9]*)([eE]?)', statusPos) + if not num or string_byte(num, -1) == 0x2E --[[ "." ]] or string_match(statusBuf, '^.[0-9]+', statusPos) then + decode_error("invalid number '" .. get_word() .. "'") + end + if c ~= '' then + num = string_match(statusBuf, '^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]', statusPos) + if not num then + decode_error("invalid number '" .. get_word() .. "'") + end + end + statusPos = statusPos + #num + return tonumber(num) +end + +local function decode_number_negative() + statusPos = statusPos + 1 + local c = string_byte(statusBuf, statusPos) + if c then + if c == 0x30 then + return -decode_number_zero() + elseif c > 0x30 and c < 0x3A then + return -decode_number() + end + end + decode_error("invalid number '" .. get_word() .. "'") +end + +local function decode_true() + if string_sub(statusBuf, statusPos, statusPos+3) ~= "true" then + decode_error("invalid literal '" .. get_word() .. "'") + end + statusPos = statusPos + 4 + return true +end + +local function decode_false() + if string_sub(statusBuf, statusPos, statusPos+4) ~= "false" then + decode_error("invalid literal '" .. get_word() .. "'") + end + statusPos = statusPos + 5 + return false +end + +local function decode_null() + if string_sub(statusBuf, statusPos, statusPos+3) ~= "null" then + decode_error("invalid literal '" .. get_word() .. "'") + end + statusPos = statusPos + 4 + return json.null +end + +local function decode_array(ast) + statusPos = statusPos + 1 + local res = {} + local chr = next_byte() + if chr == 93 --[[ ']' ]] then + statusPos = statusPos + 1 + return res + end + statusTop = statusTop + 1 + statusAry[statusTop] = true + statusRef[statusTop] = res + statusAst[statusTop] = ast + return res +end + +local function decode_object(ast) + statusPos = statusPos + 1 + local res = {} + local chr = next_byte() + if chr == 125 --[[ ']' ]] then + statusPos = statusPos + 1 + return json.createEmptyObject() + end + statusTop = statusTop + 1 + statusAry[statusTop] = false + statusRef[statusTop] = res + statusAst[statusTop] = ast + return res +end + +local decode_uncompleted_map = { + [ string_byte '"' ] = decode_string, + [ string_byte "0" ] = decode_number_zero, + [ string_byte "1" ] = decode_number, + [ string_byte "2" ] = decode_number, + [ string_byte "3" ] = decode_number, + [ string_byte "4" ] = decode_number, + [ string_byte "5" ] = decode_number, + [ string_byte "6" ] = decode_number, + [ string_byte "7" ] = decode_number, + [ string_byte "8" ] = decode_number, + [ string_byte "9" ] = decode_number, + [ string_byte "-" ] = decode_number_negative, + [ string_byte "t" ] = decode_true, + [ string_byte "f" ] = decode_false, + [ string_byte "n" ] = decode_null, + [ string_byte "[" ] = decode_array, + [ string_byte "{" ] = decode_object, +} +local function unexpected_character() + decode_error("unexpected character '" .. string_sub(statusBuf, statusPos, statusPos) .. "'") +end +local function unexpected_eol() + decode_error("unexpected character ''") +end + +local decode_map = {} +for i = 0, 255 do + decode_map[i] = decode_uncompleted_map[i] or unexpected_character +end +decode_map[-1] = unexpected_eol + +local function decode() + local chr = next_byte() + local ast = {s = statusPos, d = statusTop} + ast.v = decode_map[chr](ast) + ast.f = statusPos + return ast +end + +local function decode_item() + local top = statusTop + local ref = statusRef[top] + if statusAry[top] then + ref[#ref+1] = decode() + else + local start = statusPos + local key = decode_string() + local finish = statusPos + if next_byte() ~= 58 --[[ ':' ]] then + decode_error "expected ':'" + end + statusPos = statusPos + 1 + local val = decode() + val.key_s = start + val.key_f = finish + ref[key] = val + end + if top == statusTop then + repeat + local chr = next_byte(); statusPos = statusPos + 1 + if chr == 44 --[[ "," ]] then + local c = next_byte() + if statusAry[statusTop] then + if c ~= 93 --[[ "]" ]] then return end + else + if c ~= 125 --[[ "}" ]] then return end + end + statusPos = statusPos + 1 + else + if statusAry[statusTop] then + if chr ~= 93 --[[ "]" ]] then decode_error "expected ']' or ','" end + else + if chr ~= 125 --[[ "}" ]] then decode_error "expected '}' or ','" end + end + end + local ast = statusAst[statusTop] + ast.f = statusPos + statusTop = statusTop - 1 + until statusTop == 0 + end +end + +local JsonEmpty = function () end + +---@return {s: integer, d:integer, f:integer, v: any} +local function decode_ast(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + statusBuf = str + statusPos = 1 + statusTop = 0 + if next_byte() == -1 then + return {s = statusPos, d = statusTop, f = statusPos, v = JsonEmpty} + end + local res = decode() + while statusTop > 0 do + decode_item() + end + if string_find(statusBuf, "[^ \t\r\n]", statusPos) then + decode_error "trailing garbage" + end + return res +end + +local function split(s) + local r = {} + s:gsub('[^/]+', function (w) + r[#r+1] = w:gsub("~1", "/"):gsub("~0", "~") + end) + return r +end + +local function query_(ast, pathlst, n) + local data = ast.v + if type(data) ~= "table" then + return nil, string_format("path `%s` does not point to object or array", "/"..table_concat(pathlst, "/", 1, n-1)) + end + local k = pathlst[n] + local isarray = not json.isObject(data) + if isarray then + if k == "-" then + k = #data + 1 + else + if k:match "^0%d+" then + return nil, string_format("path `%s` point to array, but invalid", "/"..table_concat(pathlst, "/", 1, n)) + end + k = tonumber(k) + if k == nil or math_type(k) ~= "integer" or k <= 0 or k > #data + 1 then + return nil, string_format("path `%s` point to array, but invalid", "/"..table_concat(pathlst, "/", 1, n)) + end + end + end + if n == #pathlst then + return ast, k, isarray + end + local v = data[k] + if v == nil then + return ast, k, isarray, table_move(pathlst, n + 1, #pathlst, 1, {}) + end + return query_(v, pathlst, n + 1) +end + +local function split_path(path) + if type(path) ~= "string" then + return nil, "path is not a string" + end + if path:sub(1,1) ~= "/" then + return nil, "path must start with `/`" + end + return split(path:sub(2)) +end + +local function query(ast, path) + return query_(ast, split_path(path), 1) +end + +local function del_first_empty_line(str) + local pos = str:match("()[ \t]*$") + if pos then + local nl1 = str:sub(pos-1, pos-1) + if nl1:match "[\r\n]" then + return pos-1 + end + end +end + +local function del_last_empty_line(str) + local pos = str:match("^[ \t]*()") + if pos then + local nl1 = str:sub(pos, pos) + if nl1:match "[\r\n]" then + local nl2 = str:sub(pos+1, pos+1) + if nl2:match "[\r\n]" and nl1 ~= nl2 then + return pos+2 + else + return pos+1 + end + end + end +end + +local function find_max_node(t) + local max + for _, n in pairs(t) do + if not max or max.f < n.f then + max = n + end + end + return max +end + +local function encode_newline(option) + return option.newline..string_rep(option.indent, option.depth) +end + +local function apply_array_insert_before(str, option, value, node) + local start_text = str:sub(1, node.s-1) + local finish_text = str:sub(node.s) + option.depth = option.depth + node.d + local bd = {} + bd[#bd+1] = start_text + json._beautify_builder(bd, value, option) + bd[#bd+1] = "," + bd[#bd+1] = encode_newline(option) + bd[#bd+1] = finish_text + return table_concat(bd) +end + +local function apply_array_insert_after(str, option, value, node) + local start_text = str:sub(1, node.f-1) + local finish_text = str:sub(node.f) + option.depth = option.depth + node.d + local bd = {} + bd[#bd+1] = start_text + bd[#bd+1] = "," + bd[#bd+1] = encode_newline(option) + json._beautify_builder(bd, value, option) + bd[#bd+1] = finish_text + return table_concat(bd) +end + +local function apply_array_insert_empty(str, option, value, node) + local start_text = str:sub(1, node.s) + local finish_text = str:sub(node.f-1) + option.depth = option.depth + node.d + 1 + local bd = {} + bd[#bd+1] = start_text + bd[#bd+1] = encode_newline(option) + json._beautify_builder(bd, value, option) + option.depth = option.depth - 1 + bd[#bd+1] = encode_newline(option) + bd[#bd+1] = finish_text + return table_concat(bd) +end + +local function apply_replace(str, option, value, node) + local start_text = str:sub(1, node.s-1) + local finish_text = str:sub(node.f) + option.depth = option.depth + node.d + local bd = {} + bd[#bd+1] = start_text + json._beautify_builder(bd, value, option) + bd[#bd+1] = finish_text + return table_concat(bd) +end + +local function apply_object_insert(str, option, value, t, k) + local node = find_max_node(t.v) + if node then + local start_text = str:sub(1, node.f-1) + local finish_text = str:sub(node.f) + option.depth = option.depth + node.d + local bd = {} + bd[#bd+1] = start_text + bd[#bd+1] = "," + bd[#bd+1] = encode_newline(option) + bd[#bd+1] = '"' + bd[#bd+1] = json._encode_string(k) + bd[#bd+1] = '": ' + json._beautify_builder(bd, value, option) + bd[#bd+1] = finish_text + return table_concat(bd) + else + local start_text = str:sub(1, t.s) + local finish_text = str:sub(t.f-1) + option.depth = option.depth + t.d + 1 + local bd = {} + bd[#bd+1] = start_text + bd[#bd+1] = encode_newline(option) + bd[#bd+1] = '"' + bd[#bd+1] = json._encode_string(k) + bd[#bd+1] = '": ' + json._beautify_builder(bd, value, option) + option.depth = option.depth - 1 + bd[#bd+1] = encode_newline(option) + bd[#bd+1] = finish_text + return table_concat(bd) + end +end + +local function apply_remove(str, s, f) + local start_text = str:sub(1, s-1) + local finish_text = str:sub(f+1) + local start_pos = del_first_empty_line(start_text) + local finish_pos = del_last_empty_line(finish_text) + if start_pos and finish_pos then + return start_text:sub(1,start_pos) .. finish_text:sub(finish_pos) + else + return start_text .. finish_text + end +end + +local function add_prefix(v, pathlst) + for i = #pathlst, 1, -1 do + v = { [pathlst[i]] = v } + end + return v +end + +local OP = {} + +function OP.add(str, option, path, value) + if path == '/' then + return json.beautify(value, option) + end + local ast = decode_ast(str) + if ast.v == JsonEmpty then + local pathlst, err = split_path(path) + if not pathlst then + error(err) + return + end + value = add_prefix(value, pathlst) + return json.beautify(value, option) + end + local t, k, isarray, lastpath = query(ast, path) + if not t then + error(k) + return + end + if lastpath then + value = add_prefix(value, lastpath) + end + if isarray then + if t.v[k] then + return apply_array_insert_before(str, option, value, t.v[k]) + elseif k == 1 then + return apply_array_insert_empty(str, option, value, t) + else + return apply_array_insert_after(str, option, value, t.v[k-1]) + end + else + if t.v[k] then + return apply_replace(str, option, value, t.v[k]) + else + return apply_object_insert(str, option, value, t, k) + end + end +end + +function OP.remove(str, _, path) + if path == '/' then + return '' + end + local ast = decode_ast(str) + if ast.v == JsonEmpty then + return '' + end + local t, k, isarray, lastpath = query(ast, path) + if not t then + error(k) + return + end + if lastpath then + --warning: path does not exist + return str + end + if isarray then + if k > #t.v then + --warning: path does not exist + return str + end + return apply_remove(str, t.v[k].s, t.v[k].f) + else + if t.v[k] == nil then + --warning: path does not exist + return str + end + return apply_remove(str, t.v[k].key_s, t.v[k].f) + end +end + +function OP.replace(str, option, path, value) + if path == '/' then + return json.beautify(value, option) + end + local ast = decode_ast(str) + if ast.v == JsonEmpty then + local pathlst, err = split_path(path) + if not pathlst then + error(err) + return + end + value = add_prefix(value, pathlst) + return json.beautify(value, option) + end + local t, k, isarray, lastpath = query(ast, path) + if not t then + error(k) + return + end + if lastpath then + value = add_prefix(value, lastpath) + end + if t.v[k] then + return apply_replace(str, option, value, t.v[k]) + else + if isarray then + if k == 1 then + return apply_array_insert_empty(str, option, value, t) + else + return apply_array_insert_after(str, option, value, t.v[k-1]) + end + else + return apply_object_insert(str, option, value, t, k) + end + end +end + +local function edit(str, patch, option) + local f = OP[patch.op] + if not f then + error(string_format("invalid op: %s", patch.op)) + return + end + option = json._beautify_option(option) + return f(str, option, patch.path, patch.value) +end + +json.edit = edit + +return json diff --git a/script/json.lua b/script/json.lua index 39d06ab98..eaccb43e0 100644 --- a/script/json.lua +++ b/script/json.lua @@ -3,7 +3,6 @@ local next = next local error = error local tonumber = tonumber local tostring = tostring -local utf8_char = utf8.char local table_concat = table.concat local table_sort = table.sort local string_char = string.char @@ -13,17 +12,72 @@ local string_match = string.match local string_gsub = string.gsub local string_sub = string.sub local string_format = string.format -local math_type = math.type local setmetatable = setmetatable local getmetatable = getmetatable local huge = math.huge local tiny = -huge +local utf8_char +local math_type + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then + local math_floor = math.floor + function utf8_char(c) + if c <= 0x7f then + return string_char(c) + elseif c <= 0x7ff then + return string_char(math_floor(c / 64) + 192, c % 64 + 128) + elseif c <= 0xffff then + return string_char( + math_floor(c / 4096) + 224, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + elseif c <= 0x10ffff then + return string_char( + math_floor(c / 262144) + 240, + math_floor(c % 262144 / 4096) + 128, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + end + error(string_format("invalid UTF-8 code '%x'", c)) + end + function math_type(v) + if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then + return "integer" + end + return "float" + end +else + utf8_char = utf8.char + math_type = math.type +end + local json = {} -json.object = {} json.supportSparseArray = true +local objectMt = {} + +function json.createEmptyObject() + return setmetatable({}, objectMt) +end + +function json.isObject(t) + if t[1] ~= nil then + return false + end + return next(t) ~= nil or getmetatable(t) == objectMt +end + +if debug and debug.upvalueid then + -- Generate a lightuserdata + json.null = debug.upvalueid(json.createEmptyObject, 1) +else + json.null = function() end +end + -- json.encode -- local statusVisited local statusBuilder @@ -65,7 +119,7 @@ encode_map["nil"] = function () end local function encode_string(v) - return string_gsub(v, '[\0-\31\\"]', encode_escape_map) + return string_gsub(v, '[%z\1-\31\\"]', encode_escape_map) end function encode_map.string(v) @@ -93,6 +147,9 @@ function encode_map.number(v) if v ~= v or v <= tiny or v >= huge then error("unexpected number value '" .. tostring(v) .. "'") end + if math_type(v) == "integer" then + return string_format('%d', v) + end return convertreal(v) end @@ -107,7 +164,7 @@ end function encode_map.table(t) local first_val = next(t) if first_val == nil then - if getmetatable(t) == json.object then + if getmetatable(t) == objectMt then return "{}" else return "[]" @@ -121,16 +178,18 @@ function encode_map.table(t) local keys = {} for k in next, t do if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end keys[#keys+1] = k end table_sort(keys) - local k = keys[1] - statusBuilder[#statusBuilder+1] = '{"' - statusBuilder[#statusBuilder+1] = encode_string(k) - statusBuilder[#statusBuilder+1] = '":' - encode(t[k]) + do + local k = keys[1] + statusBuilder[#statusBuilder+1] = '{"' + statusBuilder[#statusBuilder+1] = encode_string(k) + statusBuilder[#statusBuilder+1] = '":' + encode(t[k]) + end for i = 2, #keys do local k = keys[i] statusBuilder[#statusBuilder+1] = ',"' @@ -140,21 +199,16 @@ function encode_map.table(t) end statusVisited[t] = nil return "}" - else - local count = 0 + elseif json.supportSparseArray then local max = 0 for k in next, t do if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end - count = count + 1 if max < k then max = k end end - if not json.supportSparseArray and count ~= max then - error("sparse array are not supported") - end statusBuilder[#statusBuilder+1] = "[" encode(t[1]) for i = 2, max do @@ -163,6 +217,32 @@ function encode_map.table(t) end statusVisited[t] = nil return "]" + else + if t[1] == nil then + error("invalid table: sparse array is not supported") + end + if jit and t[0] ~= nil then + -- 0 is the first index in luajit + error("invalid table: mixed or invalid key types: "..0) + end + statusBuilder[#statusBuilder+1] = "[" + encode(t[1]) + local count = 2 + while t[count] ~= nil do + statusBuilder[#statusBuilder+1] = "," + encode(t[count]) + count = count + 1 + end + if next(t, count-1) ~= nil then + local k = next(t, count-1) + if type(k) == "number" then + error("invalid table: sparse array is not supported") + else + error("invalid table: mixed or invalid key types: "..k) + end + end + statusVisited[t] = nil + return "]" end end @@ -213,7 +293,7 @@ local function find_line() end local function decode_error(msg) - error(string_format("ERROR: %s at line %d col %d", msg, find_line())) + error(string_format("ERROR: %s at line %d col %d", msg, find_line()), 2) end local function get_word() @@ -258,7 +338,7 @@ local function decode_string() local has_escape = false local i = statusPos + 1 while true do - i = string_find(statusBuf, '["\\\0-\31]', i) + i = string_find(statusBuf, '[%z\1-\31\\"]', i) if not i then decode_error "expected closing quote for string" end @@ -369,10 +449,10 @@ end local function decode_array() statusPos = statusPos + 1 - local res = {} if consume_byte "^[ \t\r\n]*%]" then - return res + return {} end + local res = {} statusTop = statusTop + 1 statusAry[statusTop] = true statusRef[statusTop] = res @@ -381,10 +461,10 @@ end local function decode_object() statusPos = statusPos + 1 - local res = {} if consume_byte "^[ \t\r\n]*}" then - return setmetatable(res, json.object) + return json.createEmptyObject() end + local res = {} statusTop = statusTop + 1 statusAry[statusTop] = false statusRef[statusTop] = res @@ -472,7 +552,4 @@ function json.decode(str) return res end --- Generate a lightuserdata -json.null = debug.upvalueid(decode, 1) - return json diff --git a/script/jsonc.lua b/script/jsonc.lua index 0361d99be..267ab31db 100644 --- a/script/jsonc.lua +++ b/script/jsonc.lua @@ -2,24 +2,15 @@ local type = type local next = next local error = error local tonumber = tonumber -local tostring = tostring -local table_concat = table.concat -local table_sort = table.sort local string_char = string.char local string_byte = string.byte local string_find = string.find local string_match = string.match local string_gsub = string.gsub local string_sub = string.sub -local string_rep = string.rep local string_format = string.format -local setmetatable = setmetatable -local getmetatable = getmetatable -local huge = math.huge -local tiny = -huge local utf8_char -local math_type if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then local math_floor = math.floor @@ -42,57 +33,13 @@ if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then c % 64 + 128 ) end - error(string.format("invalid UTF-8 code '%x'", c)) - end - function math_type(v) - if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then - return "integer" - end - return "float" + error(string_format("invalid UTF-8 code '%x'", c)) end else utf8_char = utf8.char - math_type = math.type -end - -local json = {} - -json.supportSparseArray = true - -local objectMt = {} - -function json.createEmptyObject() - return setmetatable({}, objectMt) -end - -function json.isObject(t) - if t[1] ~= nil then - return false - end - return next(t) ~= nil or getmetatable(t) == objectMt end -if debug and debug.upvalueid then - -- Generate a lightuserdata - json.null = debug.upvalueid(json.createEmptyObject, 1) -else - json.null = function() end -end - --- json.encode -- - -local statusVisited -local statusBuilder -local statusDep -local statusOpt - -local defaultOpt = { - newline = "", - indent = "", -} -defaultOpt.__index = defaultOpt - -local encode_map = {} +local json = require "json" local encode_escape_map = { [ "\"" ] = "\\\"", @@ -112,182 +59,6 @@ for k, v in next, encode_escape_map do decode_escape_set[string_byte(v, 2)] = true end -for i = 0, 31 do - local c = string_char(i) - if not encode_escape_map[c] then - encode_escape_map[c] = string_format("\\u%04x", i) - end -end - -encode_map["nil"] = function () - return "null" -end - -local function encode_string(v) - return string_gsub(v, '[%z\1-\31\\"]', encode_escape_map) -end - -local function convertreal(v) - local g = string_format('%.16g', v) - if tonumber(g) == v then - return g - end - return string_format('%.17g', v) -end - -if string_match(tostring(1/2), "%p") == "," then - local _convertreal = convertreal - function convertreal(v) - return string_gsub(_convertreal(v), ',', '.') - end -end - -function encode_map.number(v) - if v ~= v or v <= tiny or v >= huge then - error("unexpected number value '" .. tostring(v) .. "'") - end - if math_type(v) == "integer" then - return string_format('%d', v) - end - return convertreal(v) -end - -function encode_map.boolean(v) - if v then - return "true" - else - return "false" - end -end - -local function encode_unexpected(v) - if v == json.null then - return "null" - else - error("unexpected type '"..type(v).."'") - end -end -encode_map[ "function" ] = encode_unexpected -encode_map[ "userdata" ] = encode_unexpected -encode_map[ "thread" ] = encode_unexpected - -local function encode_newline() - statusBuilder[#statusBuilder+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) -end - -local function encode(v) - local res = encode_map[type(v)](v) - statusBuilder[#statusBuilder+1] = res -end - -function encode_map.string(v) - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(v) - return '"' -end - -function encode_map.table(t) - local first_val = next(t) - if first_val == nil then - if getmetatable(t) == objectMt then - return "{}" - else - return "[]" - end - end - if statusVisited[t] then - error("circular reference") - end - statusVisited[t] = true - if type(first_val) == 'string' then - local key = {} - for k in next, t do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - key[#key+1] = k - end - table_sort(key) - statusBuilder[#statusBuilder+1] = "{" - statusDep = statusDep + 1 - encode_newline() - local k = key[1] - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(k) - statusBuilder[#statusBuilder+1] = '": ' - encode(t[k]) - for i = 2, #key do - local k = key[i] - statusBuilder[#statusBuilder+1] = "," - encode_newline() - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(k) - statusBuilder[#statusBuilder+1] = '": ' - encode(t[k]) - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "}" - elseif json.supportSparseArray then - local max = 0 - for k in next, t do - if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") - end - if max < k then - max = k - end - end - statusBuilder[#statusBuilder+1] = "[" - statusDep = statusDep + 1 - encode_newline() - encode(t[1]) - for i = 2, max do - statusBuilder[#statusBuilder+1] = "," - encode_newline() - encode(t[i]) - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "]" - else - if t[1] == nil then - error("invalid table: mixed or invalid key types") - end - statusBuilder[#statusBuilder+1] = "[" - statusDep = statusDep + 1 - encode_newline() - encode(t[1]) - local count = 2 - while t[count] ~= nil do - statusBuilder[#statusBuilder+1] = "," - encode_newline() - encode(t[count]) - count = count + 1 - end - if next(t, count-1) ~= nil then - error("invalid table: mixed or invalid key types") - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "]" - end -end - -function json.encode(v, option) - statusVisited = {} - statusBuilder = {} - statusDep = 0 - statusOpt = option and setmetatable(option, defaultOpt) or defaultOpt - encode(v) - return table_concat(statusBuilder) -end - --- json.decode -- - local statusBuf local statusPos local statusTop @@ -580,7 +351,7 @@ local function decode_item() end end -function json.decode(str) +function json.decode_jsonc(str) if type(str) ~= "string" then error("expected argument of type string, got " .. type(str)) end diff --git a/script/jsonrpc.lua b/script/jsonrpc.lua index 7411fee80..6de6da5b8 100644 --- a/script/jsonrpc.lua +++ b/script/jsonrpc.lua @@ -14,17 +14,23 @@ function m.encode(pack) return buf end +---@param reader fun(arg: integer):string local function readProtoHead(reader) local head = {} + local line = '' while true do - local line = reader 'L' - if line == nil then + local char = reader(1) + if char == nil then -- ่ฏดๆ˜Ž็ฎก้“ๅทฒ็ปๅ…ณ้—ญไบ† return nil, 'Disconnected!' end + line = line .. char if line == '\r\n' then break end + if line:sub(-2) ~= '\r\n' then + goto continue + end local k, v = line:match '^([^:]+)%s*%:%s*(.+)\r\n$' if not k then return nil, 'Proto header error: ' .. line @@ -33,10 +39,13 @@ local function readProtoHead(reader) v = tonumber(v) end head[k] = v + line = '' + ::continue:: end return head end +---@param reader fun(arg: integer):string function m.decode(reader) local head, err = readProtoHead(reader) if not head then diff --git a/script/lazy-cacher.lua b/script/lazy-cacher.lua new file mode 100644 index 000000000..d6b29ba9d --- /dev/null +++ b/script/lazy-cacher.lua @@ -0,0 +1,171 @@ +local fs = require 'bee.filesystem' +local linkedTable = require 'linked-table' + +local setmt = setmetatable +local pairs = pairs +local iopen = io.open +local mmax = math.max + +_ENV = nil + +---@class lazy-cacher +---@field _opening linked-table +---@field _openingMap table +---@field _dir string +local mt = {} +mt.__index = mt +mt.type = 'lazy-cacher' + +mt.maxOpendFiles = 50 +mt.maxFileSize = 100 * 1024 * 1024 -- 100MB +mt.openingFiles = {} + +mt.errorHandler = function (err) end + +---@param fileID string +function mt:_closeFile(fileID) + self._opening:pop(fileID) + self._openingMap[fileID]:close() + self._openingMap[fileID] = nil +end + +---@param fileID string +---@return file*? +---@return string? errorMessage +function mt:_getFile(fileID) + if self._openingMap[fileID] then + self._opening:pop(fileID) + self._opening:pushTail(fileID) + return self._openingMap[fileID] + end + local fullPath = self._dir .. '/' .. fileID + local file, err = iopen(fullPath, 'a+b') + if not file then + return nil, err + end + self._opening:pushTail(fileID) + self._openingMap[fileID] = file + if self._opening:getSize() > self.maxOpendFiles then + local oldest = self._opening:getHead() + self:_closeFile(oldest) + end + return file +end + +---@param fileID string +---@return fun(id: integer, code: string): boolean +---@return fun(id: integer): string? +function mt:writterAndReader(fileID) + local maxFileSize = self.maxFileSize + local map = {} + ---@param file file* + local function resize(file) + local codes = {} + for id, data in pairs(map) do + local offset = data // 1000000 + local len = data % 1000000 + local suc, err = file:seek('set', offset) + if not suc then + self.errorHandler(err) + return + end + local code = file:read(len) + codes[id] = code + end + + self:_closeFile(fileID) + local fullPath = self._dir .. '/' .. fileID + local file, err = iopen(fullPath, 'wb') + if not file then + self.errorHandler(err) + return + end + + local offset = 0 + for id, code in pairs(codes) do + file:write(code) + map[id] = offset * 1000000 + #code + offset = offset + #code + end + file:close() + end + ---@param id integer + ---@param code string + ---@return boolean + local function writter(id, code) + if not code then + map[id] = nil + return true + end + if #code > 1000000 then + return false + end + local file, err = self:_getFile(fileID) + if not file then + self.errorHandler(err) + return false + end + local offset, err = file:seek('end') + if not offset then + self.errorHandler(err) + return false + end + if offset > maxFileSize then + resize(file) + file, err = self:_getFile(fileID) + if not file then + self.errorHandler(err) + return false + end + offset, err = file:seek('end') + if not offset then + self.errorHandler(err) + return false + end + maxFileSize = mmax(maxFileSize, (offset + #code) * 2) + end + local suc, err = file:write(code) + if not suc then + self.errorHandler(err) + return false + end + map[id] = offset * 1000000 + #code + return true + end + ---@param id integer + ---@return string? + local function reader(id) + if not map[id] then + return nil + end + local file, err = self:_getFile(fileID) + if not file then + self.errorHandler(err) + return nil + end + local offset = map[id] // 1000000 + local len = map[id] % 1000000 + local suc, err = file:seek('set', offset) + if not suc then + self.errorHandler(err) + return nil + end + local code = file:read(len) + return code + end + return writter, reader +end + +---@param dir string +---@param errorHandle? fun(string) +---@return lazy-cacher? +return function (dir, errorHandle) + fs.create_directories(fs.path(dir)) + local self = setmt({ + _dir = dir, + _opening = linkedTable(), + _openingMap = {}, + errorHandler = errorHandle, + }, mt) + return self +end diff --git a/script/lazytable.lua b/script/lazytable.lua new file mode 100644 index 000000000..f964f236c --- /dev/null +++ b/script/lazytable.lua @@ -0,0 +1,353 @@ +local type = type +local pairs = pairs +local error = error +local next = next +local load = load +local setmt = setmetatable +local rawset = rawset +local sdump = string.dump +local sbyte = string.byte +local smatch = string.match +local sformat = string.format +local tconcat = table.concat + +_ENV = nil + +---@class lazytable.builder +---@field source table +---@field codeMap table +---@field dumpMark table +---@field excludes table +---@field refMap table +---@field instMap table +local mt = {} +mt.__index = mt +mt.tableID = 1 +mt.keyID = 1 + +local DUMMY = function() end + +local RESERVED = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, + ['goto'] = true +} + +---@param k string|integer +---@return string +local function formatKey(k) + if type(k) == 'string' then + if not RESERVED[k] and smatch(k, '^[%a_][%w_]*$') then + return k + else + return sformat('[%q]', k) + end + end + if type(k) == 'number' then + return sformat('[%q]', k) + end + error('invalid key type: ' .. type(k)) +end + +---@param v string|number|boolean +local function formatValue(v) + return sformat('%q', v) +end + +---@param info {[1]: table, [2]: integer, [3]: table?} +---@return string +local function dump(info) + local codeBuf = {} + + codeBuf[#codeBuf + 1] = 'return{{' + local hasFields + for k, v in pairs(info[1]) do + if hasFields then + codeBuf[#codeBuf + 1] = ',' + else + hasFields = true + end + codeBuf[#codeBuf+1] = sformat('%s=%s' + , formatKey(k) + , formatValue(v) + ) + end + codeBuf[#codeBuf+1] = '}' + + codeBuf[#codeBuf+1] = sformat(',%d', formatValue(info[2])) + + if info[3] then + codeBuf[#codeBuf+1] = ',{' + hasFields = false + for k, v in pairs(info[3]) do + if hasFields then + codeBuf[#codeBuf+1] = ',' + else + hasFields = true + end + codeBuf[#codeBuf+1] = sformat('%s=%s' + , formatKey(k) + , formatValue(v) + ) + end + codeBuf[#codeBuf+1] = '}' + end + + codeBuf[#codeBuf + 1] = '}' + + return tconcat(codeBuf) +end + +---@param obj table|function|userdata|thread +---@return integer +function mt:getObjectID(obj) + if self.dumpMark[obj] then + return self.dumpMark[obj] + end + local id = self.tableID + self.tableID = self.tableID + 1 + self.dumpMark[obj] = id + if self.excludes[obj] or type(obj) ~= 'table' then + self.refMap[obj] = id + self.instMap[id] = obj + return id + end + + if not next(obj) then + self.codeMap[id] = nil + return id + end + + local fields = {} + local objs + for k, v in pairs(obj) do + local tp = type(v) + if tp == 'string' or tp == 'number' or tp == 'boolean' then + fields[k] = v + else + if not objs then + objs = {} + end + objs[k] = self:getObjectID(v) + end + end + + local code = dump({fields, #obj, objs}) + + self.codeMap[id] = code + return id +end + +---@param writter fun(id: integer, code: string): boolean +---@param reader fun(id: integer): string? +function mt:bind(writter, reader) + setmt(self.codeMap, { + __newindex = function (t, id, code) + local suc = writter(id, code) + if not suc then + rawset(t, id, code) + end + end, + __index = function (_, id) + return reader(id) + end + }) +end + +---@param t table +function mt:exclude(t) + self.excludes[t] = true + return self +end + +---@return table +function mt:entry() + local entryID = self:getObjectID(self.source) + + local codeMap = self.codeMap + local refMap = self.refMap + local instMap = self.instMap + local tableID = self.tableID + ---@type table + local idMap = {} + ---@type table + local infoMap = setmt({}, { + __mode = 'v', + __index = function (map, t) + local id = idMap[t] + local code = codeMap[id] + if not code then + return nil + end + local f = load(code) + if not f then + return nil + end + --if sbyte(code, 1, 1) ~= 27 then + -- codeMap[id] = sdump(f, true) + --end + local info = f() + map[t] = info + return info + end + }) + + local lazyload = { + ref = refMap, + __index = function(t, k) + local info = infoMap[t] + if not info then + return nil + end + local fields = info[1] + + local keyID = k + + local v = fields[keyID] + if v ~= nil then + return v + end + + local refs = info[3] + if not refs then + return nil + end + + local ref = refs[keyID] + if not ref then + return nil + end + + return instMap[ref] + end, + __newindex = function(t, k, v) + local info = infoMap[t] + local fields = info and info[1] or {} + local len = info and info[2] or 0 + local objs = info and info[3] + fields[k] = nil + if objs then + objs[k] = nil + end + if v ~= nil then + local tp = type(v) + if tp == 'string' or tp == 'number' or tp == 'boolean' then + fields[k] = v + else + if not objs then + objs = {} + end + local id = refMap[v] or idMap[v] + if not id then + id = tableID + refMap[v] = id -- ๆ–ฐ่ต‹ๅ€ผ็š„ๅฏน่ฑกไธ€ๅฎšไผš่ขซๅผ•็”จไฝ + instMap[id] = v + tableID = tableID + 1 + end + objs[k] = id + end + end + info = { fields, len, objs } + local id = idMap[t] + local code = dump(info) + infoMap[id] = nil + codeMap[id] = nil + codeMap[id] = code + end, + __len = function (t) + local info = infoMap[t] + if not info then + return 0 + end + return info[2] + end, + __pairs = function (t) + local info = infoMap[t] + if not info then + return DUMMY + end + local fields = info[1] + local objs = info[3] + local keys = {} + for k in pairs(fields) do + keys[#keys+1] = k + end + if objs then + for k in pairs(objs) do + keys[#keys+1] = k + end + end + local i = 0 + return function() + i = i + 1 + local k = keys[i] + return k, t[k] + end + end, + } + + setmt(idMap, { __mode = 'k' }) + + setmt(instMap, { + __mode = 'v', + __index = function (map, id) + local inst = {} + idMap[inst] = id + map[id] = inst + + return setmt(inst, lazyload) + end, + }) + + local entry = instMap[entryID] --[[@as table]] + + self.source = nil + self.dumpMark = nil + + return entry +end + +---@class lazytable +local m = {} + +---@param t table +---@param writter? fun(id: integer, code: string): boolean +---@param reader? fun(id: integer): string? +---@return lazytable.builder +function m.build(t, writter, reader) + local builder = setmt({ + source = t, + codeMap = {}, + refMap = {}, + instMap = {}, + dumpMark = {}, + excludes = setmt({}, { __mode = 'k' }), + }, mt) + + if writter and reader then + builder:bind(writter, reader) + end + + return builder +end + +return m diff --git a/script/lclient.lua b/script/lclient.lua index e1504e61a..13b431b0c 100644 --- a/script/lclient.lua +++ b/script/lclient.lua @@ -24,6 +24,7 @@ function mt:__close() end function mt:_fakeProto() + ---@diagnostic disable-next-line: duplicate-set-field proto.send = function (data) self._outs[#self._outs+1] = data if self.onSend then @@ -47,6 +48,7 @@ function mt:_localLoadFile() ---@async ---@param name string ---@param params any + ---@diagnostic disable-next-line: duplicate-set-field pub.awaitTask = function (name, params) if name == 'loadFile' then local path = params diff --git a/script/library.lua b/script/library.lua index 0271d3a6a..e3fe3d50e 100644 --- a/script/library.lua +++ b/script/library.lua @@ -9,11 +9,12 @@ local fsu = require 'fs-utility' local define = require "proto.define" local files = require 'files' local await = require 'await' -local timer = require 'timer' local encoder = require 'encoder' local ws = require 'workspace.workspace' local scope = require 'workspace.scope' local inspect = require 'inspect' +local jsonb = require 'json-beautify' +local jsonc = require 'jsonc' local m = {} @@ -60,7 +61,7 @@ end local function convertLink(uri, text) local fmt = getDocFormater(uri) - return text:gsub('%$([%.%w]+)', function (name) + return text:gsub('%$([%.%w_%:]+)', function (name) local lastDot = '' if name:sub(-1) == '.' then name = name:sub(1, -2) @@ -90,7 +91,7 @@ local function createViewDocument(name) if not fmt then return nil end - name = name:match '[%w_%.]+' + name = name:match '[%w_%.%:]+' if name:sub(-1) == '.' then name = name:sub(1, -2) end @@ -197,10 +198,14 @@ local function compileSingleMetaDoc(uri, script, metaLang, status) if not suc then log.debug('MiddleScript:\n', middleScript) end + local text = table.concat(compileBuf) if disable and status == 'default' then - return nil + return text, false + end + if status == 'disable' then + return text, false end - return table.concat(compileBuf) + return text, true end local function loadMetaLocale(langID, result) @@ -231,11 +236,8 @@ local function initBuiltIn(uri) loadMetaLocale(langID, metaLang) end - if scp:get('metaPath') == metaPath:string() then - log.debug('Has meta path, skip:', metaPath:string()) - return - end - scp:set('metaPath', metaPath:string()) + local metaPaths = {} + scp:set('metaPaths', metaPaths) local suc = xpcall(function () if not fs.exists(metaPath) then fs.create_directories(metaPath) @@ -250,19 +252,31 @@ local function initBuiltIn(uri) for libName, status in pairs(define.BuiltIn) do status = config.get(uri, 'Lua.runtime.builtin')[libName] or status log.debug('Builtin status:', libName, status) - if status == 'disable' then - goto CONTINUE - end - libName = libName .. '.lua' + ---@type fs.path - local libPath = templateDir / libName - local metaDoc = compileSingleMetaDoc(uri, fsu.loadFile(libPath), metaLang, status) + local libPath = templateDir / (libName .. '.lua') + local metaDoc, include = compileSingleMetaDoc(uri, fsu.loadFile(libPath), metaLang, status) if metaDoc then metaDoc = encoder.encode(encoding, metaDoc, 'auto') - out:saveFile(libName, metaDoc) - local outputPath = metaPath / libName + + local outputLibName = libName:gsub('%.', '/') .. '.lua' + if outputLibName ~= libName then + out:createDirectories(fs.path(outputLibName):parent_path()) + end + + local ok, err = out:saveFile(outputLibName, metaDoc) + if not ok then + log.debug("Save Meta File Failed:", err) + goto CONTINUE + end + + local outputPath = metaPath / outputLibName m.metaPaths[outputPath:string()] = true log.debug('Meta path:', outputPath:string()) + + if include then + metaPaths[#metaPaths+1] = outputPath:string() + end end ::CONTINUE:: end @@ -273,17 +287,80 @@ local function initBuiltIn(uri) end ---@param libraryDir fs.path -local function loadSingle3rdConfig(libraryDir) - local configText = fsu.loadFile(libraryDir / 'config.lua') +---@return table? +local function loadSingle3rdConfigFromJson(libraryDir) + local path = libraryDir / 'config.json' + local configText = fsu.loadFile(path) + if not configText then + return nil + end + + local suc, cfg = xpcall(jsonc.decode_jsonc, function (err) + log.error('Decode config.json failed at:', libraryDir:string(), err) + end, configText) + if not suc then + return nil + end + + if type(cfg) ~= 'table' then + log.error('config.json must be an object:', libraryDir:string()) + return nil + end + + return cfg +end + +---@param libraryDir fs.path +---@return table? +local function loadSingle3rdConfigFromLua(libraryDir) + local path = libraryDir / 'config.lua' + local configText = fsu.loadFile(path) if not configText then return nil end local env = setmetatable({}, { __index = _G }) - assert(load(configText, '@' .. libraryDir:string(), 't', env))() + local f, err = load(configText, '@' .. libraryDir:string(), 't', env) + if not f then + log.error('Load config.lua failed at:', libraryDir:string(), err) + return nil + end + + local suc = xpcall(f, function (err) + log.error('Load config.lua failed at:', libraryDir:string(), err) + end) + + if not suc then + return nil + end ---@type Config3rdParty local cfg = {} + for k, v in pairs(env) do + cfg[k] = v + end + + return cfg +end + +---@param libraryDir fs.path +local function loadSingle3rdConfig(libraryDir) + local cfg = loadSingle3rdConfigFromJson(libraryDir) + if not cfg then + cfg = loadSingle3rdConfigFromLua(libraryDir) + if not cfg then + return + end + local jsonbuf = jsonb.beautify(cfg) + client.requestMessage('Info', lang.script.WINDOW_CONFIG_LUA_DEPRECATED, { + lang.script.WINDOW_CONVERT_CONFIG_LUA, + }, function (action, index) + if index == 1 and jsonbuf then + fsu.saveFile(libraryDir / 'config.json', jsonbuf) + fsu.fileRemove(libraryDir / 'config.lua') + end + end) + end cfg.path = libraryDir:filename():string() cfg.name = cfg.name or cfg.path @@ -292,10 +369,6 @@ local function loadSingle3rdConfig(libraryDir) cfg.plugin = true end - for k, v in pairs(env) do - cfg[k] = v - end - if cfg.words then for i, word in ipairs(cfg.words) do cfg.words[i] = '()' .. word .. '()' @@ -355,15 +428,37 @@ end ---@param onlyMemory boolean local function apply3rd(uri, cfg, onlyMemory) local changes = {} - if cfg.configs then - for _, change in ipairs(cfg.configs) do - changes[#changes+1] = { - key = change.key, - action = change.action, - prop = change.prop, - value = change.value, - uri = uri, - } + if cfg.settings then + for key, value in pairs(cfg.settings) do + if type(value) == 'table' then + if #value == 0 then + for k, v in pairs(value) do + changes[#changes+1] = { + key = key, + action = 'prop', + prop = k, + value = v, + uri = uri, + } + end + else + for _, v in ipairs(value) do + changes[#changes+1] = { + key = key, + action = 'add', + value = v, + uri = uri, + } + end + end + else + changes[#changes+1] = { + key = key, + action = 'set', + value = value, + uri = uri, + } + end end end @@ -386,7 +481,7 @@ local function apply3rd(uri, cfg, onlyMemory) client.setConfig(changes, onlyMemory) end -local hasAsked +local hasAsked = {} ---@async ---@param uri string ---@param cfg Config3rdParty @@ -395,46 +490,41 @@ local function askFor3rd(uri, cfg) if hasAsked then return nil end - hasAsked = true - local yes1 = lang.script.WINDOW_APPLY_WHIT_SETTING - local yes2 = lang.script.WINDOW_APPLY_WHITOUT_SETTING - local no = lang.script.WINDOW_DONT_SHOW_AGAIN - local result = client.awaitRequestMessage('Info' - , lang.script('WINDOW_ASK_APPLY_LIBRARY', cfg.name) - , {yes1, yes2, no} - ) - if not result then - return nil - end - if result == yes1 then + + if checkThirdParty == 'Apply' then apply3rd(uri, cfg, false) - client.setConfig({ - { - key = 'Lua.workspace.checkThirdParty', - action = 'set', - value = false, - uri = uri, - }, - }, false) - elseif result == yes2 then + elseif checkThirdParty == 'ApplyInMemory' then apply3rd(uri, cfg, true) - client.setConfig({ - { - key = 'Lua.workspace.checkThirdParty', - action = 'set', - value = false, - uri = uri, - }, - }, true) - else - client.setConfig({ - { - key = 'Lua.workspace.checkThirdParty', - action = 'set', - value = false, - uri = uri, - }, - }, false) + elseif checkThirdParty == 'Disable' then + return nil + elseif checkThirdParty == 'Ask' then + hasAsked[cfg.name] = true + local applyAndSetConfig = lang.script.WINDOW_APPLY_WHIT_SETTING + local applyInMemory = lang.script.WINDOW_APPLY_WHITOUT_SETTING + local dontShowAgain = lang.script.WINDOW_DONT_SHOW_AGAIN + local result = client.awaitRequestMessage('Info' + , lang.script('WINDOW_ASK_APPLY_LIBRARY', cfg.name) + , {applyAndSetConfig, applyInMemory, dontShowAgain} + ) + if not result then + -- "If none got selected" + -- See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessageRequest + return nil + end + if result == applyAndSetConfig then + apply3rd(uri, cfg, false) + elseif result == applyInMemory then + apply3rd(uri, cfg, true) + else + client.setConfig({ + { + key = 'Lua.workspace.checkThirdParty', + action = 'set', + value = 'Disable', + uri = uri, + }, + }, false) + end end end @@ -474,23 +564,33 @@ local function check3rdByWords(uri, configs) return end for _, cfg in ipairs(configs) do - if cfg.words then - for _, word in ipairs(cfg.words) do - await.delay() - if wholeMatch(text, word) then - askFor3rd(uri, cfg) - return - end + if not cfg.words then + goto CONTINUE + end + if hasAsked[cfg.name] then + goto CONTINUE + end + local library = ('%s/library'):format(cfg.dirname) + if util.arrayHas(config.get(uri, 'Lua.workspace.library'), library) then + goto CONTINUE + end + for _, word in ipairs(cfg.words) do + await.delay() + if wholeMatch(text, word) then + log.info('Found 3rd library by word: ', word, uri, library, inspect(config.get(uri, 'Lua.workspace.library'))) + ---@async + await.call(function () + askFor3rd(uri, cfg, checkThirdParty) + end) + return end end + ::CONTINUE:: end end, id) end -local function check3rdByFileName(uri, configs) - if hasAsked then - return - end +local function check3rdByFileName(uri, configs, checkThirdParty) local path = ws.getRelativePath(uri) if not path then return @@ -500,29 +600,43 @@ local function check3rdByFileName(uri, configs) await.call(function () ---@async await.sleep(0.1) for _, cfg in ipairs(configs) do - if cfg.files then - for _, filename in ipairs(cfg.files) do - await.delay() - if wholeMatch(path, filename) then - askFor3rd(uri, cfg) - return - end + if not cfg.files then + goto CONTINUE + end + if hasAsked[cfg.name] then + goto CONTINUE + end + local library = ('%s/library'):format(cfg.dirname) + if util.arrayHas(config.get(uri, 'Lua.workspace.library'), library) then + goto CONTINUE + end + for _, filename in ipairs(cfg.files) do + await.delay() + if wholeMatch(path, filename) then + log.info('Found 3rd library by filename: ', filename, uri, library, inspect(config.get(uri, 'Lua.workspace.library'))) + ---@async + await.call(function () + askFor3rd(uri, cfg, checkThirdParty) + end) + return end end + ::CONTINUE:: end end, id) end ---@async local function check3rd(uri) - if hasAsked then - return - end if ws.isIgnored(uri) then return end - if not config.get(uri, 'Lua.workspace.checkThirdParty') then + local checkThirdParty = config.get(uri, 'Lua.workspace.checkThirdParty') + -- Backwards compatability: `checkThirdParty` used to be a boolean. + if not checkThirdParty or checkThirdParty == 'Disable' then return + elseif checkThirdParty == true then + checkThirdParty = 'Ask' end local scp = scope.getScope(uri) if not scp:get 'canCheckThirdParty' then @@ -532,8 +646,8 @@ local function check3rd(uri) if not thirdConfigs then return end - check3rdByWords(uri, thirdConfigs) - check3rdByFileName(uri, thirdConfigs) + check3rdByWords(uri, thirdConfigs, checkThirdParty) + check3rdByFileName(uri, thirdConfigs, checkThirdParty) end local function check3rdOfWorkspace(suri) @@ -569,6 +683,7 @@ end) files.watch(function (ev, uri) if ev == 'update' or ev == 'dll' then + await.sleep(1) check3rd(uri) end end) diff --git a/script/log.lua b/script/log.lua index 6cb865c3e..8e94dd0e5 100644 --- a/script/log.lua +++ b/script/log.lua @@ -127,7 +127,7 @@ function m.raw(thd, level, msg, source, currentline, clock) end end - if log.print then + if m.print then print(buf) end diff --git a/script/meta/bee/filesystem.lua b/script/meta/bee/filesystem.lua index f6cdff793..c4267b976 100644 --- a/script/meta/bee/filesystem.lua +++ b/script/meta/bee/filesystem.lua @@ -1,3 +1,5 @@ +---@meta + ---@class fs.path ---@operator div: fs.path local fsPath = {} @@ -18,14 +20,22 @@ end function fsPath:filename() end +---@return fs.path +function fsPath:stem() +end + +---@return fs.path +function fsPath:extension() +end + ---@class fs.status local fsStatus = {} ----@return string +---@return 'none' | 'not_found' | 'regular' | 'directory' | 'symlink' | 'block' | 'character' | 'fifo' | 'junction' | 'unknown' function fsStatus:type() end ----@class fs +---@class bee.filesystem local fs = {} ---@class fs.copy_options @@ -34,7 +44,7 @@ local copy_options fs.copy_options = copy_options ----@param path string +---@param path string|fs.path ---@return fs.path function fs.path(path) end @@ -54,7 +64,7 @@ function fs.is_directory(path) end ---@param path fs.path ----@return fun():fs.path +---@return fun():fs.path, fs.status function fs.pairs(path) end @@ -63,6 +73,11 @@ end function fs.canonical(path) end +---@param path fs.path +---@return fs.path +function fs.fullpath(path) +end + ---@param path fs.path ---@return fs.path function fs.absolute(path) @@ -84,8 +99,17 @@ end ---@param source fs.path ---@param target fs.path ----@param options? `fs.copy_options.overwrite_existing` +---@param options? integer | `fs.copy_options.overwrite_existing` function fs.copy_file(source, target, options) end +---@param oldPath fs.path +---@param newPath fs.path +function fs.rename(oldPath, newPath) +end + +---@return fs.path +function fs.current_path() +end + return fs diff --git a/script/meta/bee/filewatch.lua b/script/meta/bee/filewatch.lua new file mode 100644 index 000000000..b5211355e --- /dev/null +++ b/script/meta/bee/filewatch.lua @@ -0,0 +1,32 @@ +---@meta + +---@class bee.filewatch.instance +local instance = {} + +---@param path string +function instance:add(path) +end + +---@param enable boolean +---@return boolean +function instance:set_recursive(enable) +end + +---@param enable boolean +---@return boolean +function instance:set_follow_symlinks(enable) +end + +---@param callback? fun(path: string):boolean +---@return boolean +function instance:set_filter(callback) +end + +---@class bee.filewatch +local fw = {} + +---@return bee.filewatch.instance +function fw.create() +end + +return fw diff --git a/script/meta/bee/socket.lua b/script/meta/bee/socket.lua new file mode 100644 index 000000000..1724cbb39 --- /dev/null +++ b/script/meta/bee/socket.lua @@ -0,0 +1,69 @@ +---@meta + +---@alias bee.socket.protocol +---| 'tcp' +---| 'udp' +---| 'unix' +---| 'tcp6' +---| 'udp6' + +---@class bee.socket +---@overload fun(protocol: bee.socket.protocol): bee.socket.fd?, string? +local socket = {} + +---@param readfds? bee.socket.fd[] +---@param writefds? bee.socket.fd[] +---@param timeout number +---@return bee.socket.fd[] # readfds +---@return bee.socket.fd[] # writefds +function socket.select(readfds, writefds, timeout) end + +---@param handle lightuserdata +---@return bee.socket.fd +function socket.fd(handle) end + +---@return bee.socket.fd +---@return bee.socket.fd +function socket.pair() end + +---@class bee.socket.fd +local fd = {} + +---@param addr string +---@param port? integer +---@return boolean +---@return string? +function fd:bind(addr, port) end + +function fd:close() end + +---@return boolean +---@return string? +function fd:listen() end + +---@param addr string +---@param port integer +---@return boolean +---@return string? +function fd:connect(addr, port) end + +---@param len? integer +---@return string | false +function fd:recv(len) end + +---@param content string +function fd:send(content) end + +---@return lightuserdata +function fd:handle() end + +---@return lightuserdata +function fd:detach() end + +---@return boolean +function fd:status() end + +---@return bee.socket.fd +function fd:accept() end + +return socket diff --git a/script/meta/bee/thread.lua b/script/meta/bee/thread.lua new file mode 100644 index 000000000..6b4323a4b --- /dev/null +++ b/script/meta/bee/thread.lua @@ -0,0 +1,33 @@ +---@meta + +---@class bee.thread +local thread = {} + +---@param time number +function thread.sleep(time) end + +---@param name string +function thread.newchannel(name) end + +---@param name string +---@return bee.thread.channel +function thread.channel(name) end + +---@param script string +---@return bee.thread.thread +function thread.thread(script) end + +---@class bee.thread.channel +local channel = {} + +function channel:push(...) end + +---@return ... +function channel:pop() end + +---@return ... +function channel:bpop() end + +---@class bee.thread.thread + +return thread diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 39249a4ce..4b2142d18 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -118,6 +118,7 @@ local Specials = { ['assert'] = true, ['error'] = true, ['type'] = true, + ['os.exit'] = true, } local UnarySymbol = { @@ -232,12 +233,22 @@ local ListFinishMap = { ['while'] = true, } -local State, Lua, Line, LineOffset, Chunk, Tokens, Index, LastTokenFinish, Mode, LocalCount +local State, Lua, Line, LineOffset, Chunk, Tokens, Index, LastTokenFinish, Mode, LocalCount, LocalLimited local LocalLimit = 200 local parseExp, parseAction, pushActionIntoCurrentChunk, parseMultiVars +---@class parser.state.err +---@field type string +---@field start? parser.position +---@field finish? parser.position +---@field info? table +---@field fix? table +---@field version? string[]|string +---@field level? string | 'Error' | 'Warning' + +---@type fun(err:parser.state.err):parser.state.err|nil local pushError local function addSpecial(name, obj) @@ -431,6 +442,19 @@ local function resolveLongString(finishMark) : gsub('\r\n?', '\n') stringResult = result end + if finishMark == ']]' and State.version == 'Lua 5.1' then + local nestOffset = sfind(Lua, '[[', start, true) + if nestOffset and nestOffset < finishOffset then + fastForwardToken(nestOffset) + local nestStartPos = getPosition(nestOffset, 'left') + local nestFinishPos = getPosition(nestOffset + 1, 'right') + pushError { + type = 'NESTING_LONG_MARK', + start = nestStartPos, + finish = nestFinishPos, + } + end + end fastForwardToken(finishOffset + #finishMark) if miss then local pos = getPosition(finishOffset - 1, 'right') @@ -456,7 +480,7 @@ end local function parseLongString() local start, finish, mark = sfind(Lua, '^(%[%=*%[)', Tokens[Index]) - if not mark then + if not start then return nil end fastForwardToken(finish + 1) @@ -697,6 +721,7 @@ local function parseLocalAttrs() return attrs end +---@param obj table local function createLocal(obj, attrs) obj.type = 'local' obj.effect = obj.finish @@ -715,7 +740,8 @@ local function createLocal(obj, attrs) end locals[#locals+1] = obj LocalCount = LocalCount + 1 - if LocalCount > LocalLimit then + if not LocalLimited and LocalCount > LocalLimit then + LocalLimited = true pushError { type = 'LOCAL_LIMIT', start = obj.start, @@ -1365,12 +1391,24 @@ local function parseNumber() return result end -local function isKeyWord(word) +local function isKeyWord(word, nextToken) if KeyWord[word] then return true end if word == 'goto' then - return State.version ~= 'Lua 5.1' + if State.version == 'Lua 5.1' then + return false + end + if State.version == 'LuaJIT' then + if not nextToken then + return false + end + if CharMapWord[ssub(nextToken, 1, 1)] then + return true + end + return false + end + return true end return false end @@ -1396,7 +1434,7 @@ local function parseName(asAction) finish = finishPos, } end - if isKeyWord(word) then + if isKeyWord(word, Tokens[Index + 1]) then pushError { type = 'KEYWORD', start = startPos, @@ -1444,68 +1482,6 @@ local function parseNameOrList(parent) return list or first end -local function dropTail() - local token = Tokens[Index + 1] - if token ~= '?' - and token ~= ':' then - return - end - local pl, pt, pp = 0, 0, 0 - while true do - local token = Tokens[Index + 1] - if not token then - break - end - if NLMap[token] then - break - end - if token == ',' then - if pl > 0 - or pt > 0 - or pp > 0 then - goto CONTINUE - else - break - end - end - if token == '<' then - pl = pl + 1 - goto CONTINUE - end - if token == '{' then - pt = pt + 1 - goto CONTINUE - end - if token == '(' then - pp = pp + 1 - goto CONTINUE - end - if token == '>' then - if pl <= 0 then - break - end - pl = pl - 1 - goto CONTINUE - end - if token == '}' then - if pt <= 0 then - break - end - pt = pt - 1 - goto CONTINUE - end - if token == ')' then - if pp <= 0 then - break - end - pp = pp - 1 - goto CONTINUE - end - ::CONTINUE:: - Index = Index + 2 - end -end - local function parseExpList(mini) local list local wantSep = false @@ -1539,7 +1515,7 @@ local function parseExpList(mini) break end local nextToken = peekWord() - if isKeyWord(nextToken) + if isKeyWord(nextToken, Tokens[Index + 2]) and nextToken ~= 'function' and nextToken ~= 'true' and nextToken ~= 'false' @@ -1552,7 +1528,6 @@ local function parseExpList(mini) if not exp then break end - dropTail() if wantSep then missSymbol(',', list[#list].finish, exp.start) end @@ -1745,6 +1720,9 @@ local function parseTable() end local function addDummySelf(node, call) + if not node then + return + end if node.type ~= 'getmethod' then return end @@ -1767,7 +1745,43 @@ local function addDummySelf(node, call) tinsert(call.args, 1, self) end +local function checkAmbiguityCall(call, parenPos) + if State.version ~= 'Lua 5.1' then + return + end + local node = call.node + if not node then + return + end + local nodeRow = guide.rowColOf(node.finish) + local callRow = guide.rowColOf(parenPos) + if nodeRow == callRow then + return + end + pushError { + type = 'AMBIGUOUS_SYNTAX', + start = parenPos, + finish = call.finish, + } +end + +local function bindSpecial(source, name) + if Specials[name] then + addSpecial(name, source) + else + local ospeicals = State.options.special + if ospeicals and ospeicals[name] then + addSpecial(ospeicals[name], source) + end + end +end + local function parseSimple(node, funcName) + local currentName + if node.type == 'getglobal' + or node.type == 'getlocal' then + currentName = node[1] + end local lastMethod while true do if lastMethod and node.node == lastMethod then @@ -1798,6 +1812,16 @@ local function parseSimple(node, funcName) if field then field.parent = getfield field.type = 'field' + if currentName then + if node.type == 'getlocal' + or node.type == 'getglobal' + or node.type == 'getfield' then + currentName = currentName .. '.' .. field[1] + bindSpecial(getfield, currentName) + else + currentName = nil + end + end else pushError { type = 'MISS_FIELD', @@ -1869,6 +1893,7 @@ local function parseSimple(node, funcName) call.args = args end addDummySelf(node, call) + checkAmbiguityCall(call, startPos) node.parent = call node = call elseif token == '{' then @@ -1966,6 +1991,12 @@ local function parseSimple(node, funcName) and node.node == lastMethod then lastMethod = nil end + if node.type == 'call' then + if node.node.special == 'error' + or node.node.special == 'os.exit' then + node.hasExit = true + end + end if node == lastMethod then if funcName then lastMethod = nil @@ -2101,14 +2132,7 @@ local function resolveName(node) end end local name = node[1] - if Specials[name] then - addSpecial(name, node) - else - local ospeicals = State.options.special - if ospeicals and ospeicals[name] then - addSpecial(ospeicals[name], node) - end - end + bindSpecial(node, name) return node end @@ -2249,7 +2273,7 @@ local function parseParams(params) finish = getPosition(Tokens[Index] + #token - 1, 'right'), } end - if isKeyWord(token) then + if isKeyWord(token, Tokens[Index + 3]) then pushError { type = 'KEYWORD', start = getPosition(Tokens[Index], 'left'), @@ -2272,14 +2296,13 @@ local function parseFunction(isLocal, isAction) type = 'function', start = funcLeft, finish = funcRight, + bstart = funcRight, keyword = { [1] = funcLeft, [2] = funcRight, }, } Index = Index + 2 - local LastLocalCount = LocalCount - LocalCount = 0 skipSpace(true) local hasLeftParen = Tokens[Index + 1] == '(' if not hasLeftParen then @@ -2308,6 +2331,7 @@ local function parseFunction(isLocal, isAction) end func.name = simple func.finish = simple.finish + func.bstart = simple.finish if not isAction then simple.parent = func pushError { @@ -2320,11 +2344,18 @@ local function parseFunction(isLocal, isAction) hasLeftParen = Tokens[Index + 1] == '(' end end + local LastLocalCount = LocalCount + LocalCount = 0 pushChunk(func) local params if func.name and func.name.type == 'getmethod' then if func.name.type == 'getmethod' then - params = {} + params = { + type = 'funcargs', + start = funcRight, + finish = funcRight, + parent = func + } params[1] = createLocal { start = funcRight, finish = funcRight, @@ -2335,20 +2366,20 @@ local function parseFunction(isLocal, isAction) end end if hasLeftParen then + params = params or {} local parenLeft = getPosition(Tokens[Index], 'left') Index = Index + 2 params = parseParams(params) - if params then - params.type = 'funcargs' - params.start = parenLeft - params.finish = lastRightPosition() - params.parent = func - func.args = params - end + params.type = 'funcargs' + params.start = parenLeft + params.finish = lastRightPosition() + params.parent = func + func.args = params skipSpace(true) if Tokens[Index + 1] == ')' then local parenRight = getPosition(Tokens[Index], 'right') func.finish = parenRight + func.bstart = parenRight if params then params.finish = parenRight end @@ -2356,6 +2387,7 @@ local function parseFunction(isLocal, isAction) skipSpace(true) else func.finish = lastRightPosition() + func.bstart = func.finish if params then params.finish = func.finish end @@ -2381,6 +2413,37 @@ local function parseFunction(isLocal, isAction) return func end +local function checkNeedParen(source) + local token = Tokens[Index + 1] + if token ~= '.' + and token ~= ':' then + return source + end + local exp = parseSimple(source, false) + if exp == source then + return exp + end + pushError { + type = 'NEED_PAREN', + start = source.start, + finish = source.finish, + fix = { + title = 'FIX_ADD_PAREN', + { + start = source.start, + finish = source.start, + text = '(', + }, + { + start = source.finish, + finish = source.finish, + text = ')', + } + } + } + return exp +end + local function parseExpUnit() local token = Tokens[Index + 1] if token == '(' then @@ -2395,17 +2458,29 @@ local function parseExpUnit() if token == '{' then local table = parseTable() - return table + if not table then + return nil + end + local exp = checkNeedParen(table) + return exp end if CharMapStrSH[token] then local string = parseShortString() - return string + if not string then + return nil + end + local exp = checkNeedParen(string) + return exp end if CharMapStrLH[token] then local string = parseLongString() - return string + if not string then + return nil + end + local exp = checkNeedParen(string) + return exp end local number = parseNumber() @@ -2432,7 +2507,10 @@ local function parseExpUnit() local node = parseName() if node then - return parseSimple(resolveName(node), false) + local nameNode = resolveName(node) + if nameNode then + return parseSimple(nameNode, false) + end end return nil @@ -2857,6 +2935,8 @@ local function compileExpAsAction(exp) local isLocal if exp.type == 'getlocal' and exp[1] == State.ENVMode then exp.special = nil + -- TODO: need + 1 at the end + LocalCount = LocalCount - 1 local loc = createLocal(exp, parseLocalAttrs()) loc.locPos = exp.start loc.effect = maxinteger @@ -2871,14 +2951,14 @@ local function compileExpAsAction(exp) end if exp.type == 'call' then - if exp.node.special == 'error' then + if exp.hasExit then for i = #Chunk, 1, -1 do local block = Chunk[i] if block.type == 'ifblock' or block.type == 'elseifblock' or block.type == 'elseblock' or block.type == 'function' then - block.hasError = true + block.hasExit = true break end end @@ -2968,6 +3048,7 @@ local function parseDo() type = 'do', start = doLeft, finish = doRight, + bstart = doRight, keyword = { [1] = doLeft, [2] = doRight, @@ -3150,6 +3231,7 @@ local function parseIfBlock(parent) parent = parent, start = ifLeft, finish = ifRight, + bstart = ifRight, keyword = { [1] = ifLeft, [2] = ifRight, @@ -3160,7 +3242,8 @@ local function parseIfBlock(parent) if filter then ifblock.filter = filter ifblock.finish = filter.finish - filter.parent = ifblock + ifblock.bstart = ifblock.finish + filter.parent = ifblock else missExp() end @@ -3169,6 +3252,7 @@ local function parseIfBlock(parent) if thenToken == 'then' or thenToken == 'do' then ifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right') + ifblock.bstart = ifblock.finish ifblock.keyword[3] = getPosition(Tokens[Index], 'left') ifblock.keyword[4] = ifblock.finish if thenToken == 'do' then @@ -3193,7 +3277,7 @@ local function parseIfBlock(parent) pushChunk(ifblock) parseActions() popChunk() - ifblock.finish = lastRightPosition() + ifblock.finish = getPosition(Tokens[Index], 'left') if ifblock.locals then LocalCount = LocalCount - #ifblock.locals end @@ -3208,6 +3292,7 @@ local function parseElseIfBlock(parent) parent = parent, start = ifLeft, finish = ifRight, + bstart = ifRight, keyword = { [1] = ifLeft, [2] = ifRight, @@ -3219,6 +3304,7 @@ local function parseElseIfBlock(parent) if filter then elseifblock.filter = filter elseifblock.finish = filter.finish + elseifblock.bstart = elseifblock.finish filter.parent = elseifblock else missExp() @@ -3228,6 +3314,7 @@ local function parseElseIfBlock(parent) if thenToken == 'then' or thenToken == 'do' then elseifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right') + elseifblock.bstart = elseifblock.finish elseifblock.keyword[3] = getPosition(Tokens[Index], 'left') elseifblock.keyword[4] = elseifblock.finish if thenToken == 'do' then @@ -3252,7 +3339,7 @@ local function parseElseIfBlock(parent) pushChunk(elseifblock) parseActions() popChunk() - elseifblock.finish = lastRightPosition() + elseifblock.finish = getPosition(Tokens[Index], 'left') if elseifblock.locals then LocalCount = LocalCount - #elseifblock.locals end @@ -3267,6 +3354,7 @@ local function parseElseBlock(parent) parent = parent, start = ifLeft, finish = ifRight, + bstart = ifRight, keyword = { [1] = ifLeft, [2] = ifRight, @@ -3277,7 +3365,7 @@ local function parseElseBlock(parent) pushChunk(elseblock) parseActions() popChunk() - elseblock.finish = lastRightPosition() + elseblock.finish = getPosition(Tokens[Index], 'left') if elseblock.locals then LocalCount = LocalCount - #elseblock.locals end @@ -3342,6 +3430,7 @@ local function parseFor() finish = getPosition(Tokens[Index] + 2, 'right'), keyword = {}, } + action.bstart = action.finish action.keyword[1] = action.start action.keyword[2] = action.finish Index = Index + 2 @@ -3353,6 +3442,7 @@ local function parseFor() missName() end skipSpace() + local forStateVars -- for i = if expectAssign() then action.type = 'loop' @@ -3367,10 +3457,15 @@ local function parseFor() name = nameOrList[1] end end + -- for x in ... uses 4 variables + forStateVars = 3 + LocalCount = LocalCount + forStateVars if name then + ---@cast name parser.object local loc = createLocal(name) loc.parent = action action.finish = name.finish + action.bstart = action.finish action.loc = loc end if expList then @@ -3380,12 +3475,14 @@ local function parseFor() value.parent = expList action.init = value action.finish = expList[#expList].finish + action.bstart = action.finish end local max = expList[2] if max then max.parent = expList action.max = max action.finish = max.finish + action.bstart = action.finish else pushError { type = 'MISS_LOOP_MAX', @@ -3398,6 +3495,7 @@ local function parseFor() step.parent = expList action.step = step action.finish = step.finish + action.bstart = action.finish end else pushError { @@ -3419,7 +3517,8 @@ local function parseFor() local exps = parseExpList() - action.finish = inRight + action.finish = inRight + action.bstart = action.finish action.keyword[3] = inLeft action.keyword[4] = inRight @@ -3440,6 +3539,7 @@ local function parseFor() local lastExp = exps[#exps] if lastExp then action.finish = lastExp.finish + action.bstart = action.finish end action.exps = exps @@ -3452,12 +3552,21 @@ local function parseFor() missExp() end + if State.version == 'Lua 5.4' then + forStateVars = 4 + else + forStateVars = 3 + end + LocalCount = LocalCount + forStateVars + if list then local lastName = list[#list] list.range = lastName and lastName.range or inRight action.keys = list for i = 1, #list do - local loc = createLocal(list[i]) + local obj = list[i] + ---@cast obj parser.object + local loc = createLocal(obj) loc.parent = action loc.effect = action.finish end @@ -3473,6 +3582,7 @@ local function parseFor() local left = getPosition(Tokens[Index], 'left') local right = getPosition(Tokens[Index] + #doToken - 1, 'right') action.finish = left + action.bstart = action.finish action.keyword[#action.keyword+1] = left action.keyword[#action.keyword+1] = right if doToken == 'then' then @@ -3512,6 +3622,9 @@ local function parseFor() if action.locals then LocalCount = LocalCount - #action.locals end + if forStateVars then + LocalCount = LocalCount - forStateVars + end return action end @@ -3523,6 +3636,7 @@ local function parseWhile() finish = getPosition(Tokens[Index] + 4, 'right'), keyword = {}, } + action.bstart = action.finish action.keyword[1] = action.start action.keyword[2] = action.finish Index = Index + 2 @@ -3547,6 +3661,7 @@ local function parseWhile() local left = getPosition(Tokens[Index], 'left') local right = getPosition(Tokens[Index] + #doToken - 1, 'right') action.finish = left + action.bstart = action.finish action.keyword[#action.keyword+1] = left action.keyword[#action.keyword+1] = right if doToken == 'then' then @@ -3599,6 +3714,7 @@ local function parseRepeat() finish = getPosition(Tokens[Index] + 5, 'right'), keyword = {}, } + action.bstart = action.finish action.keyword[1] = action.start action.keyword[2] = action.finish Index = Index + 2 @@ -3736,7 +3852,7 @@ function parseAction() return parseRepeat() end - if token == 'goto' and isKeyWord 'goto' then + if token == 'goto' and isKeyWord('goto', Tokens[Index + 3]) then return parseGoTo() end @@ -3908,11 +4024,13 @@ local function initState(lua, version, options) LineOffset = 1 LastTokenFinish = 0 LocalCount = 0 + LocalLimited = false Chunk = {} Tokens = tokens(lua) Index = 1 ---@class parser.state ---@field uri uri + ---@field lines integer[] local state = { version = version, lua = lua, @@ -3921,6 +4039,7 @@ local function initState(lua, version, options) comms = {}, lines = { [0] = 1, + size = #lua, }, options = options or {}, } @@ -3949,8 +4068,6 @@ local function initState(lua, version, options) errs[#errs+1] = err return err end - - state.pushError = pushError end return function (lua, mode, version, options) diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 8f024bc74..f7ca8a25e 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -10,7 +10,7 @@ local type = type ---@field type string ---@field special string ---@field tag string ----@field args { [integer]: parser.object, start: integer, finish: integer } +---@field args { [integer]: parser.object, start: integer, finish: integer, type: string } ---@field locals parser.object[] ---@field returns? parser.object[] ---@field breaks? parser.object[] @@ -21,6 +21,7 @@ local type = type ---@field finish integer ---@field range integer ---@field effect integer +---@field bstart integer ---@field attrs string[] ---@field specials parser.object[] ---@field labels parser.object[] @@ -55,7 +56,7 @@ local type = type ---@field returnIndex integer ---@field assignIndex integer ---@field docIndex integer ----@field docs parser.object[] +---@field docs parser.object ---@field state table ---@field comment table ---@field optional boolean @@ -71,9 +72,14 @@ local type = type ---@field hasGoTo? true ---@field hasReturn? true ---@field hasBreak? true ----@field hasError? true +---@field hasExit? true ---@field [integer] parser.object|any ----@field _root parser.object +---@field dot { type: string, start: integer, finish: integer } +---@field colon { type: string, start: integer, finish: integer } +---@field package _root parser.object +---@field package _eachCache? parser.object[] +---@field package _isGlobal? boolean +---@field package _typeCache? parser.object[][] ---@class guide ---@field debugMode boolean @@ -81,6 +87,10 @@ local m = {} m.ANY = {""} +m.notNamePattern = '[^%w_\x80-\xff]' +m.namePattern = '[%a_\x80-\xff][%w_\x80-\xff]*' +m.namePatternFull = '^' .. m.namePattern .. '$' + local blockTypes = { ['while'] = true, ['in'] = true, @@ -95,6 +105,16 @@ local blockTypes = { ['main'] = true, } +local topBlockTypes = { + ['while'] = true, + ['function'] = true, + ['if'] = true, + ['ifblock'] = true, + ['elseblock'] = true, + ['elseifblock'] = true, + ['main'] = true, +} + local breakBlockTypes = { ['while'] = true, ['in'] = true, @@ -109,40 +129,40 @@ local childMap = { ['while'] = {'filter', '#'}, ['in'] = {'keys', 'exps', '#'}, ['loop'] = {'loc', 'init', 'max', 'step', '#'}, + ['do'] = {'#'}, ['if'] = {'#'}, ['ifblock'] = {'filter', '#'}, ['elseifblock'] = {'filter', '#'}, ['elseblock'] = {'#'}, ['setfield'] = {'node', 'field', 'value'}, + ['getfield'] = {'node', 'field'}, + ['setmethod'] = {'node', 'method', 'value'}, + ['getmethod'] = {'node', 'method'}, + ['setindex'] = {'node', 'index', 'value'}, + ['getindex'] = {'node', 'index'}, + ['tableindex'] = {'index', 'value'}, + ['tablefield'] = {'field', 'value'}, + ['tableexp'] = {'value'}, ['setglobal'] = {'value'}, ['local'] = {'attrs', 'value'}, ['setlocal'] = {'value'}, ['return'] = {'#'}, - ['do'] = {'#'}, ['select'] = {'vararg'}, ['table'] = {'#'}, - ['tableindex'] = {'index', 'value'}, - ['tablefield'] = {'field', 'value'}, - ['tableexp'] = {'value'}, ['function'] = {'args', '#'}, ['funcargs'] = {'#'}, - ['setmethod'] = {'node', 'method', 'value'}, - ['getmethod'] = {'node', 'method'}, - ['setindex'] = {'node', 'index', 'value'}, - ['getindex'] = {'node', 'index'}, ['paren'] = {'exp'}, ['call'] = {'node', 'args'}, ['callargs'] = {'#'}, - ['getfield'] = {'node', 'field'}, ['list'] = {'#'}, ['binary'] = {1, 2}, ['unary'] = {1}, ['doc'] = {'#'}, - ['doc.class'] = {'class', '#extends', '#signs', 'comment'}, + ['doc.class'] = {'class', '#extends', '#signs', 'docAttr', 'comment'}, ['doc.type'] = {'#types', 'name', 'comment'}, ['doc.alias'] = {'alias', 'extends', 'comment'}, - ['doc.enum'] = {'enum', 'extends', 'comment'}, + ['doc.enum'] = {'enum', 'extends', 'comment', 'docAttr'}, ['doc.param'] = {'param', 'extends', 'comment'}, ['doc.return'] = {'#returns', 'comment'}, ['doc.field'] = {'field', 'extends', 'comment'}, @@ -157,13 +177,15 @@ local childMap = { ['doc.type.field'] = {'name', 'extends'}, ['doc.type.sign'] = {'node', '#signs'}, ['doc.overload'] = {'overload', 'comment'}, - ['doc.see'] = {'name', 'field'}, + ['doc.see'] = {'name', 'comment'}, ['doc.version'] = {'#versions'}, ['doc.diagnostic'] = {'#names'}, ['doc.as'] = {'as'}, - ['doc.cast'] = {'loc', '#casts'}, + ['doc.cast'] = {'name', '#casts'}, ['doc.cast.block'] = {'extends'}, - ['doc.operator'] = {'op', 'exp', 'extends'} + ['doc.operator'] = {'op', 'exp', 'extends'}, + ['doc.meta'] = {'name'}, + ['doc.attr'] = {'#names'}, } ---@type table @@ -278,17 +300,10 @@ function m.isLiteral(obj) end --- ่Žทๅ–ๅญ—้ข้‡ ----@param obj parser.object +---@param obj table ---@return any function m.getLiteral(obj) - local tp = obj.type - if tp == 'boolean' then - return obj[1] - elseif tp == 'string' then - return obj[1] - elseif tp == 'number' then - return obj[1] - elseif tp == 'integer' then + if m.isLiteral(obj) then return obj[1] end return nil @@ -410,6 +425,22 @@ function m.getParentType(obj, want) error('guide.getParentType overstack') end +--- ๅฏปๆ‰พๆ‰€ๅœจ็ˆถ็ฑปๅž‹ +---@param obj parser.object +---@return parser.object? +function m.getParentTypes(obj, wants) + for _ = 1, 10000 do + obj = obj.parent + if not obj then + return nil + end + if wants[obj.type] then + return obj + end + end + error('guide.getParentTypes overstack') +end + --- ๅฏปๆ‰พๆ นๅŒบๅ— ---@param obj parser.object ---@return parser.object @@ -436,7 +467,7 @@ function m.getRoot(obj) error('guide.getRoot overstack') end ----@param obj parser.object +---@param obj parser.object | { uri: uri } ---@return uri function m.getUri(obj) if obj.uri then @@ -470,6 +501,9 @@ function m.getLocal(source, name, pos) if not block then return nil end + if block.type == 'main' then + break + end if block.start <= pos and block.finish >= pos and blockTypes[block.type] then @@ -632,7 +666,7 @@ end --- ้ๅކๆ‰€ๆœ‰ๅŒ…ๅซposition็š„source ---@param ast parser.object ---@param position integer ----@param callback fun(src: parser.object) +---@param callback fun(src: parser.object): any function m.eachSourceContain(ast, position, callback) local list = { ast } local mark = {} @@ -708,7 +742,7 @@ end --- ้ๅކๆ‰€ๆœ‰ๆŒ‡ๅฎš็ฑปๅž‹็š„source ---@param ast parser.object ---@param type string ----@param callback fun(src: parser.object) +---@param callback fun(src: parser.object): any ---@return any function m.eachSourceType(ast, type, callback) local cache = getSourceTypeCache(ast) @@ -741,7 +775,7 @@ end --- ้ๅކๆ‰€ๆœ‰็š„source ---@param ast parser.object ----@param callback fun(src: parser.object) +---@param callback fun(src: parser.object): boolean? function m.eachSource(ast, callback) local cache = ast._eachCache if not cache then @@ -780,6 +814,9 @@ function m.eachChild(source, callback) end --- ่Žทๅ–ๆŒ‡ๅฎš็š„ special +---@param ast parser.object +---@param name string +---@param callback fun(src: parser.object) function m.eachSpecialOf(ast, name, callback) local root = m.getRoot(ast) local state = root.state @@ -812,12 +849,24 @@ end ---@param col integer ---@return integer function m.positionOf(row, col) - return row * 10000 + col + return row * 10000 + math.min(col, 10000 - 1) end function m.positionToOffsetByLines(lines, position) local row, col = m.rowColOf(position) - return (lines[row] or 1) + col - 1 + if row < 0 then + return 0 + end + if row > #lines then + return lines.size + end + local offset = lines[row] + col - 1 + if lines[row + 1] and offset >= lines[row + 1] then + return lines[row + 1] - 1 + elseif offset > lines.size then + return lines.size + end + return offset end --- ่ฟ”ๅ›žๅ…จๆ–‡ๅ…‰ๆ ‡ไฝ็ฝฎ @@ -872,7 +921,7 @@ function m.getLineRange(state, row) return 0 end -local isSetMap = { +local assignTypeMap = { ['setglobal'] = true, ['local'] = true, ['self'] = true, @@ -882,7 +931,6 @@ local isSetMap = { ['setindex'] = true, ['tablefield'] = true, ['tableindex'] = true, - ['tableexp'] = true, ['label'] = true, ['doc.class'] = true, ['doc.alias'] = true, @@ -895,9 +943,9 @@ local isSetMap = { ['doc.type.field'] = true, ['doc.type.array'] = true, } -function m.isSet(source) +function m.isAssign(source) local tp = source.type - if isSetMap[tp] then + if assignTypeMap[tp] then return true end if tp == 'call' then @@ -909,7 +957,7 @@ function m.isSet(source) return false end -local isGetMap = { +local getTypeMap = { ['getglobal'] = true, ['getlocal'] = true, ['getfield'] = true, @@ -918,7 +966,7 @@ local isGetMap = { } function m.isGet(source) local tp = source.type - if isGetMap[tp] then + if getTypeMap[tp] then return true end if tp == 'call' then @@ -945,26 +993,14 @@ function m.getKeyNameOfLiteral(obj) if tp == 'field' or tp == 'method' then return obj[1] - elseif tp == 'string' then - local s = obj[1] - if s then - return s - end - elseif tp == 'number' then - local n = obj[1] - if n then - return obj[1] - end - elseif tp == 'integer' then - local n = obj[1] - if n then - return obj[1] - end - elseif tp == 'boolean' then - local b = obj[1] - if b then - return b - end + elseif tp == 'string' + or tp == 'number' + or tp == 'integer' + or tp == 'boolean' + or tp == 'doc.type.integer' + or tp == 'doc.type.string' + or tp == 'doc.type.boolean' then + return obj[1] end end @@ -1000,8 +1036,7 @@ function m.getKeyName(obj) elseif tp == 'tableexp' then return obj.tindex elseif tp == 'field' - or tp == 'method' - or tp == 'doc.see.field' then + or tp == 'method' then return obj[1] elseif tp == 'doc.class' then return obj.class[1] @@ -1011,10 +1046,15 @@ function m.getKeyName(obj) return obj.enum[1] elseif tp == 'doc.field' then return obj.field[1] - elseif tp == 'doc.field.name' then + elseif tp == 'doc.field.name' + or tp == 'doc.type.name' + or tp == 'doc.class.name' + or tp == 'doc.alias.name' + or tp == 'doc.enum.name' + or tp == 'doc.extends.name' then return obj[1] elseif tp == 'doc.type.field' then - return obj.name[1] + return m.getKeyName(obj.name) end return m.getKeyNameOfLiteral(obj) end @@ -1065,8 +1105,7 @@ function m.getKeyType(obj) elseif tp == 'tableexp' then return 'integer' elseif tp == 'field' - or tp == 'method' - or tp == 'doc.see.field' then + or tp == 'method' then return 'string' elseif tp == 'doc.class' then return 'string' @@ -1309,4 +1348,106 @@ function m.isBlockType(source) return blockTypes[source.type] == true end +---@param source parser.object +---@return parser.object? +function m.getSelfNode(source) + if source.type == 'getlocal' + or source.type == 'setlocal' then + source = source.node + end + if source.type ~= 'self' then + return nil + end + local args = source.parent + if args.type == 'callargs' then + local call = args.parent + if call.type ~= 'call' then + return nil + end + local getmethod = call.node + if getmethod.type ~= 'getmethod' then + return nil + end + return getmethod.node + end + if args.type == 'funcargs' then + return m.getFunctionSelfNode(args.parent) + end + return nil +end + +---@param func parser.object +---@return parser.object? +function m.getFunctionSelfNode(func) + if func.type ~= 'function' then + return nil + end + local parent = func.parent + if parent.type == 'setmethod' + or parent.type == 'setfield' then + return parent.node + end + return nil +end + +---@param source parser.object +---@return parser.object? +function m.getTopBlock(source) + for _ = 1, 1000 do + local block = source.parent + if not block then + return nil + end + if topBlockTypes[block.type] then + return block + end + source = block + end + return nil +end + +---@param source parser.object +---@return boolean +function m.isParam(source) + if source.type ~= 'local' + and source.type ~= 'self' then + return false + end + if source.parent.type ~= 'funcargs' then + return false + end + return true +end + +---@param source parser.object +---@return parser.object[]? +function m.getParams(source) + if source.type == 'call' then + local args = source.args + if not args then + return + end + assert(args.type == 'callargs', 'call.args type is\'t callargs') + return args + elseif source.type == 'callargs' then + return source + elseif source.type == 'function' then + local args = source.args + if not args then + return + end + assert(args.type == 'funcargs', 'function.args type is\'t callargs') + return args + end + return nil +end + +---@param source parser.object +---@param index integer +---@return parser.object? +function m.getParam(source, index) + local args = m.getParams(source) + return args and args[index] or nil +end + return m diff --git a/script/parser/init.lua b/script/parser/init.lua index bc004f770..9848ce00a 100644 --- a/script/parser/init.lua +++ b/script/parser/init.lua @@ -2,7 +2,7 @@ local api = { compile = require 'parser.compile', lines = require 'parser.lines', guide = require 'parser.guide', - luadoc = require 'parser.luadoc', + luadoc = require 'parser.luadoc'.luadoc, } return api diff --git a/script/parser/lines.lua b/script/parser/lines.lua index 964aabf41..ffd021e68 100644 --- a/script/parser/lines.lua +++ b/script/parser/lines.lua @@ -6,6 +6,7 @@ return function (text) local current = 1 local lines = {} lines[0] = 1 + lines.size = #text local i = 0 while true do local pos = sfind(text,'[\r\n]', current) diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index 4f7afd6fe..9178348cf 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -63,7 +63,7 @@ Symbol <- ({} { } {}) -> Symbol ]], { - s = m.S' \t', + s = m.S' \t\v\f', ea = '\a', eb = '\b', ef = '\f', @@ -137,17 +137,26 @@ Symbol <- ({} { end, }) +---@alias parser.visibleType 'public' | 'protected' | 'private' | 'package' + ---@class parser.object ----@field literal boolean ----@field signs parser.object[] ----@field originalComment parser.object ----@field as? parser.object ----@field touch? integer ----@field module? string ----@field async? boolean ----@field versions? table[] ----@field names? parser.object[] ----@field path? string +---@field literal boolean +---@field signs parser.object[] +---@field originalComment parser.object +---@field as? parser.object +---@field touch? integer +---@field module? string +---@field async? boolean +---@field versions? table[] +---@field names? parser.object[] +---@field path? string +---@field bindComments? parser.object[] +---@field visible? parser.visibleType +---@field operators? parser.object[] +---@field calls? parser.object[] +---@field generics? parser.object[] +---@field generic? parser.object +---@field docAttr? parser.object local function parseTokens(text, offset) Ci = 0 @@ -161,8 +170,9 @@ local function parseTokens(text, offset) Ci = 0 end -local function peekToken() - return TokenTypes[Ci+1], TokenContents[Ci+1] +local function peekToken(offset) + offset = offset or 1 + return TokenTypes[Ci + offset], TokenContents[Ci + offset] end ---@return string? tokenType @@ -243,30 +253,48 @@ local function nextSymbolOrError(symbol) return false end -local function parseIndexField(tp, parent) - if not checkToken('symbol', '[', 1) then +local function parseDocAttr(parent) + if not checkToken('symbol', '(', 1) then return nil end nextToken() - 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 + + local attrs = { + type = 'doc.attr', + parent = parent, + start = getStart(), + finish = getStart(), + names = {}, + } + + while true do + if checkToken('symbol', ',', 1) then + nextToken() + goto continue + end + local name = parseName('doc.attr.name', attrs) + if not name then + break + end + attrs.names[#attrs.names+1] = name + attrs.finish = name.finish + ::continue:: + end + + nextSymbolOrError(')') + attrs.finish = getFinish() + + return attrs +end + +local function parseIndexField(parent) + if not checkToken('symbol', '[', 1) then + return nil end + nextToken() + local field = parseType(parent) + nextSymbolOrError ']' + return field end local function parseTable(parent) @@ -298,7 +326,7 @@ local function parseTable(parent) needCloseParen = true end field.name = parseName('doc.field.name', field) - or parseIndexField('doc.field.name', field) + or parseIndexField(field) if not field.name then pushWarning { type = 'LUADOC_MISS_FIELD_NAME', @@ -341,6 +369,78 @@ local function parseTable(parent) return typeUnit end +local function parseTuple(parent) + if not checkToken('symbol', '[', 1) then + return nil + end + nextToken() + local typeUnit = { + type = 'doc.type.table', + start = getStart(), + parent = parent, + fields = {}, + isTuple = true, + } + + local index = 1 + while true do + if checkToken('symbol', ']', 1) then + nextToken() + break + end + local field = { + type = 'doc.type.field', + parent = typeUnit, + } + + do + local needCloseParen + if checkToken('symbol', '(', 1) then + nextToken() + needCloseParen = true + end + field.name = { + type = 'doc.type', + start = getFinish(), + firstFinish = getFinish(), + finish = getFinish(), + parent = field, + } + field.name.types = { + [1] = { + type = 'doc.type.integer', + start = getFinish(), + finish = getFinish(), + parent = field.name, + [1] = index, + } + } + index = index + 1 + field.extends = parseType(field) + if not field.extends then + break + end + field.optional = field.extends.optional + field.start = field.extends.start + field.finish = field.extends.finish + if needCloseParen then + nextSymbolOrError ')' + end + end + + typeUnit.fields[#typeUnit.fields+1] = field + if checkToken('symbol', ',', 1) + or checkToken('symbol', ';', 1) then + nextToken() + else + nextSymbolOrError(']') + break + end + end + typeUnit.finish = getFinish() + return typeUnit +end + local function parseSigns(parent) if not checkToken('symbol', '<', 1) then return nil @@ -605,6 +705,53 @@ local function parseCode(parent) return code end +local function parseCodePattern(parent) + local tp, pattern = peekToken() + if not tp or tp ~= 'name' then + return nil + end + local codeOffset + local finishOffset + local content + for i = 2, 8 do + local next, nextContent = peekToken(i) + if not next or TokenFinishs[Ci+i-1] + 1 ~= TokenStarts[Ci+i] then + if codeOffset then + finishOffset = i + break + end + ---ไธ่ฟž็ปญ็š„name๏ผŒๆ— ๆ•ˆ็š„ + return nil + end + if next == 'code' then + if codeOffset and content ~= nextContent then + -- ๆš‚ๆ—ถไธๆ”ฏๆŒๅคšgeneric + return nil + end + codeOffset = i + pattern = pattern .. "%s" + content = nextContent + elseif next ~= 'name' then + return nil + else + pattern = pattern .. nextContent + end + end + local start = getStart() + for i = 2 , finishOffset do + nextToken() + end + local code = { + type = 'doc.type.code', + start = start, + finish = getFinish(), + parent = parent, + pattern = pattern, + [1] = content, + } + return code +end + local function parseInteger(parent) local tp, content = peekToken() if not tp or tp ~= 'integer' then @@ -654,11 +801,13 @@ end function parseTypeUnit(parent) local result = parseFunction(parent) or parseTable(parent) + or parseTuple(parent) or parseString(parent) or parseCode(parent) or parseInteger(parent) or parseBoolean(parent) or parseParen(parent) + or parseCodePattern(parent) if not result then result = parseName('doc.type.name', parent) or parseDots('doc.type.name', parent) @@ -707,6 +856,8 @@ local function parseResume(parent) return result end +local lockResume = false + function parseType(parent) local result = { type = 'doc.type', @@ -767,7 +918,7 @@ function parseType(parent) if comments then resume.comment = table.concat(comments, '\n') else - resume.comment = nextComm.text:match('#%s*(.+)', #resumeHead + 1) + resume.comment = nextComm.text:match('%s*#?%s*(.+)', resume.finish - nextComm.start) end result.types[#result.types+1] = resume result.finish = resume.finish @@ -785,20 +936,10 @@ function parseType(parent) return false end - local checkResume = true - local nsymbol, ncontent = peekToken() - if nsymbol == 'symbol' then - if ncontent == ',' - or ncontent == ':' - or ncontent == '|' - or ncontent == ')' - or ncontent == '}' then - checkResume = false - end - end - - if checkResume then + if not lockResume then + lockResume = true while pushResume() do end + lockResume = false end if #result.types == 0 then @@ -819,7 +960,9 @@ local docSwitch = util.switch() type = 'doc.class', fields = {}, operators = {}, + calls = {}, } + result.docAttr = parseDocAttr(result) result.class = parseName('doc.class.name', result) if not result.class then pushWarning { @@ -842,6 +985,7 @@ local docSwitch = util.switch() while true do local extend = parseName('doc.extends.name', result) or parseTable(result) + or parseTuple(result) if not extend then pushWarning { type = 'LUADOC_MISS_CLASS_EXTENDS_NAME', @@ -989,7 +1133,13 @@ local docSwitch = util.switch() if tp == 'name' then if value == 'public' or value == 'protected' - or value == 'private' then + or value == 'private' + or value == 'package' then + local tp2 = peekToken(1) + local tp3 = peekToken(2) + if tp2 == 'name' and not tp3 then + return false + end result.visible = value result.start = getStart() return true @@ -998,7 +1148,7 @@ local docSwitch = util.switch() return false end) result.field = parseName('doc.field.name', result) - or parseIndexField('doc.field.name', result) + or parseIndexField(result) if not result.field then pushWarning { type = 'LUADOC_MISS_FIELD_NAME', @@ -1116,11 +1266,13 @@ local docSwitch = util.switch() end) : case 'meta' : call(function () - return { + local meta = { type = 'doc.meta', start = getFinish(), finish = getFinish(), } + meta.name = parseName('doc.meta.name', meta) + return meta end) : case 'export-env' : call(function () @@ -1199,15 +1351,15 @@ local docSwitch = util.switch() } result.name = parseName('doc.see.name', result) if not result.name then + pushWarning { + type = 'LUADOC_MISS_SEE_NAME', + start = getFinish(), + finish = getFinish(), + } return nil end - result.start = result.name.start + result.start = result.name.start result.finish = result.name.finish - if checkToken('symbol', '#', 1) then - nextToken() - result.field = parseName('doc.see.field', result) - result.finish = getFinish() - end return result end) : case 'diagnostic' @@ -1332,7 +1484,7 @@ local docSwitch = util.switch() return result end - result.loc = loc + result.name = loc result.finish = loc.finish while true do @@ -1402,12 +1554,16 @@ local docSwitch = util.switch() if checkToken('symbol', '(', 1) then nextToken() - local exp = parseType(result) - if exp then - result.exp = exp - result.finish = exp.finish + if checkToken('symbol', ')', 1) then + nextToken() + else + local exp = parseType(result) + if exp then + result.exp = exp + result.finish = exp.finish + end + nextSymbolOrError ')' end - nextSymbolOrError ')' end nextSymbolOrError ':' @@ -1446,19 +1602,56 @@ local docSwitch = util.switch() end) : case 'enum' : call(function () + local attr = parseDocAttr() local name = parseName('doc.enum.name') if not name then return nil end local result = { - type = 'doc.enum', - start = name.start, - finish = name.finish, - enum = name, + type = 'doc.enum', + start = name.start, + finish = name.finish, + enum = name, + docAttr = attr, } name.parent = result + if attr then + attr.parent = result + end return result end) + : case 'private' + : call(function () + return { + type = 'doc.private', + start = getFinish(), + finish = getFinish(), + } + end) + : case 'protected' + : call(function () + return { + type = 'doc.protected', + start = getFinish(), + finish = getFinish(), + } + end) + : case 'public' + : call(function () + return { + type = 'doc.public', + start = getFinish(), + finish = getFinish(), + } + end) + : case 'package' + : call(function () + return { + type = 'doc.package', + start = getFinish(), + finish = getFinish(), + } + end) local function convertTokens(doc) local tp, text = nextToken() @@ -1479,21 +1672,22 @@ end local function trimTailComment(text) local comment = text if text:sub(1, 1) == '@' then - comment = text:sub(2) + comment = util.trim(text:sub(2)) end if text:sub(1, 1) == '#' then - comment = text:sub(2) + comment = util.trim(text:sub(2)) end if text:sub(1, 2) == '--' then - comment = text:sub(3) + comment = util.trim(text:sub(3)) end - if comment:find '^%s*[\'"[]' then + if comment:find '^%s*[\'"[]' + and comment:find '[\'"%]]%s*$' then local state = compile(comment:gsub('^%s+', ''), 'String') if state and state.ast then - comment = state.ast[1] + comment = state.ast[1] --[[@as string]] end end - return comment + return util.trim(comment) end local function buildLuaDoc(comment) @@ -1509,13 +1703,17 @@ local function buildLuaDoc(comment) comment = comment, } end + local startOffset = comment.start + if comment.type == 'comment.long' then + startOffset = startOffset + #comment.mark - 2 + end local doc = text:sub(startPos) - parseTokens(doc, comment.start + startPos) + parseTokens(doc, startOffset + startPos) local result, rests = convertTokens(doc) if result then - result.range = comment.finish + result.range = math.max(comment.finish, result.finish) local finish = result.firstFinish or result.finish if rests then for _, rest in ipairs(rests) do @@ -1610,7 +1808,7 @@ local function bindGeneric(binded) if doc.type == 'doc.generic' then for _, obj in ipairs(doc.generics) do local name = obj.generic[1] - generics[name] = true + generics[name] = obj end end if doc.type == 'doc.class' @@ -1618,7 +1816,7 @@ local function bindGeneric(binded) if doc.signs then for _, sign in ipairs(doc.signs) do local name = sign[1] - generics[name] = true + generics[name] = sign end end end @@ -1632,6 +1830,7 @@ local function bindGeneric(binded) local name = src[1] if generics[name] then src.type = 'doc.generic.name' + src.generic = generics[name] end end) guide.eachSourceType(doc, 'doc.type.code', function (src) @@ -1645,6 +1844,16 @@ local function bindGeneric(binded) end end +local function bindDocWithSource(doc, source) + if not source.bindDocs then + source.bindDocs = {} + end + if source.bindDocs[#source.bindDocs] ~= doc then + source.bindDocs[#source.bindDocs+1] = doc + end + doc.bindSource = source +end + local function bindDoc(source, binded) local isParam = source.type == 'self' or source.type == 'local' @@ -1662,11 +1871,18 @@ local function bindDoc(source, binded) or doc.type == 'doc.deprecated' or doc.type == 'doc.version' or doc.type == 'doc.module' - or doc.type == 'doc.source' then + or doc.type == 'doc.source' + or doc.type == 'doc.private' + or doc.type == 'doc.protected' + or doc.type == 'doc.public' + or doc.type == 'doc.package' + or doc.type == 'doc.see' then if source.type == 'function' or isParam then goto CONTINUE end + bindDocWithSource(doc, source) + ok = true elseif doc.type == 'doc.type' then if source.type == 'function' or isParam @@ -1674,69 +1890,70 @@ local function bindDoc(source, binded) goto CONTINUE end source._bindedDocType = true + bindDocWithSource(doc, source) + ok = true elseif doc.type == 'doc.overload' then if not source.bindDocs then source.bindDocs = {} end source.bindDocs[#source.bindDocs+1] = doc - if source.type ~= 'function' then - doc.bindSource = source + if source.type == 'function' then + bindDocWithSource(doc, source) end + ok = true elseif doc.type == 'doc.param' then - local suc if isParam and doc.param[1] == source[1] then - suc = true + bindDocWithSource(doc, source) + ok = true elseif source.type == '...' and doc.param[1] == '...' then - suc = true + bindDocWithSource(doc, source) + ok = true elseif source.type == 'self' and doc.param[1] == 'self' then - suc = true - end - if source.type == 'function' then + bindDocWithSource(doc, source) + ok = true + elseif source.type == 'function' then if not source.bindDocs then source.bindDocs = {} end - source.bindDocs[#source.bindDocs+1] = doc - end - - if not suc then - goto CONTINUE + source.bindDocs[#source.bindDocs + 1] = doc + if source.args then + for _, arg in ipairs(source.args) do + if arg[1] == doc.param[1] then + bindDocWithSource(doc, arg) + break + end + end + end end elseif doc.type == 'doc.vararg' then - if source.type ~= '...' then - goto CONTINUE + if source.type == '...' then + bindDocWithSource(doc, source) + ok = true end elseif doc.type == 'doc.return' or doc.type == 'doc.generic' or doc.type == 'doc.async' or doc.type == 'doc.nodiscard' then - if source.type ~= 'function' then - goto CONTINUE + if source.type == 'function' then + bindDocWithSource(doc, source) + ok = true end elseif doc.type == 'doc.enum' then if source.type == 'table' then - goto OK + bindDocWithSource(doc, source) + ok = true end if source.value and source.value.type == 'table' then - if not source.value.bindDocs then - source.value.bindDocs = {} - end - source.value.bindDocs[#source.value.bindDocs+1] = doc - doc.bindSource = source.value + bindDocWithSource(doc, source.value) + goto CONTINUE end - goto CONTINUE - elseif doc.type ~= 'doc.comment' then - goto CONTINUE - end - ::OK:: - if not source.bindDocs then - source.bindDocs = {} + elseif doc.type == 'doc.comment' then + bindDocWithSource(doc, source) + ok = true end - source.bindDocs[#source.bindDocs+1] = doc - doc.bindSource = source - ok = true ::CONTINUE:: end return ok @@ -1784,7 +2001,7 @@ local function bindDocsBetween(sources, binded, start, finish) or src.type == 'setindex' or src.type == 'setmethod' or src.type == 'function' - or src.type == 'table' + or src.type == 'return' or src.type == '...' then if bindDoc(src, binded) then ok = true @@ -1851,6 +2068,11 @@ local function bindCommentsAndFields(binded) end bindCommentsToDoc(doc, comments) comments = {} + elseif doc.type == 'doc.overload' then + if class then + class.calls[#class.calls+1] = doc + doc.class = class + end elseif doc.type == 'doc.alias' or doc.type == 'doc.enum' then bindCommentsToDoc(doc, comments) @@ -1880,6 +2102,14 @@ local function bindDocWithSources(sources, binded) bindGeneric(binded) bindCommentsAndFields(binded) bindReturnIndex(binded) + + -- doc is special node + if lastDoc.special then + if bindDoc(lastDoc.special, binded) then + return + end + end + local row = guide.rowColOf(lastDoc.finish) local suc = bindDocsBetween(sources, binded, guide.positionOf(row, 0), lastDoc.start) if not suc then @@ -1891,7 +2121,7 @@ local bindDocAccept = { 'local' , 'setlocal' , 'setglobal', 'setfield' , 'setmethod' , 'setindex' , 'tablefield', 'tableindex', 'self' , - 'function' , 'table' , '...' , + 'function' , 'return' , '...' , } local function bindDocs(state) @@ -1910,12 +2140,16 @@ local function bindDocs(state) state.ast.docs.groups[#state.ast.docs.groups+1] = binded end binded[#binded+1] = doc - if isTailComment(text, doc) then + if doc.specialBindGroup then + bindDocWithSources(sources, doc.specialBindGroup) + binded = nil + elseif isTailComment(text, doc) and doc.type ~= "doc.class" and doc.type ~= "doc.field" then bindDocWithSources(sources, binded) binded = nil else local nextDoc = state.ast.docs[i+1] - if not isNextLine(doc, nextDoc) then + if nextDoc and nextDoc.special + or not isNextLine(doc, nextDoc) then bindDocWithSources(sources, binded) binded = nil end @@ -1944,7 +2178,7 @@ local function findTouch(state, doc) end end -return function (state) +local function luadoc(state) local ast = state.ast local comments = state.comms table.sort(comments, function (a, b) @@ -1957,8 +2191,19 @@ return function (state) } pushWarning = function (err) + local errs = state.errs + if err.finish < err.start then + err.finish = err.start + end + local last = errs[#errs] + if last then + if last.start <= err.start and last.finish >= err.finish then + return + end + end err.level = err.level or 'Warning' - state.pushError(err) + errs[#errs+1] = err + return err end Lines = state.lines @@ -1991,6 +2236,7 @@ return function (state) if not comment then break end + lockResume = false local doc, rests = buildLuaDoc(comment) if doc then insertDoc(doc, comment) @@ -2002,6 +2248,18 @@ return function (state) end end + if ast.state.pluginDocs then + for i, doc in ipairs(ast.state.pluginDocs) do + insertDoc(doc, doc.originalComment) + end + ---@param a unknown + ---@param b unknown + table.sort(ast.docs, function (a, b) + return a.start < b.start + end) + ast.state.pluginDocs = nil + end + ast.docs.start = ast.start ast.docs.finish = ast.finish @@ -2011,3 +2269,21 @@ return function (state) bindDocs(state) end + +return { + buildAndBindDoc = function (ast, src, comment, group) + local doc = buildLuaDoc(comment) + if doc then + local pluginDocs = ast.state.pluginDocs or {} + pluginDocs[#pluginDocs+1] = doc + doc.special = src + doc.originalComment = comment + doc.virtual = true + doc.specialBindGroup = group + ast.state.pluginDocs = pluginDocs + return doc + end + return nil + end, + luadoc = luadoc +} diff --git a/script/parser/tokens.lua b/script/parser/tokens.lua index a4de7f88b..5f455beec 100644 --- a/script/parser/tokens.lua +++ b/script/parser/tokens.lua @@ -1,6 +1,6 @@ local m = require 'lpeglabel' -local Sp = m.S' \t' +local Sp = m.S' \t\v\f' local Nl = m.P'\r\n' + m.S'\r\n' local Number = m.R'09'^1 local Word = m.R('AZ', 'az', '__', '\x80\xff') * m.R('AZ', 'az', '09', '__', '\x80\xff')^0 diff --git a/script/plugin.lua b/script/plugin.lua index 870b68b6c..26211a426 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -7,6 +7,15 @@ local scope = require 'workspace.scope' local ws = require 'workspace' local fs = require 'bee.filesystem' +---@class pluginInterfaces +local pluginConfigs = { + -- create plugin for vm module + VM = { + OnCompileFunctionParam = function (next, func, source) + end + } +} + ---@class plugin local m = {} @@ -18,35 +27,55 @@ function m.showError(scp, err) client.showMessage('Error', lang.script('PLUGIN_RUNTIME_ERROR', scp:get('pluginPath'), err)) end +---@alias plugin.event 'OnSetText' | 'OnTransformAst' + +---@param event plugin.event function m.dispatch(event, uri, ...) local scp = scope.getScope(uri) - local interface = scp:get('pluginInterface') - if not interface then - return false - end - local method = interface[event] - if type(method) ~= 'function' then + local interfaces = scp:get('pluginInterfaces') + if not interfaces then return false end - local clock = os.clock() - tracy.ZoneBeginN('plugin dispatch:' .. event) - local suc, res1, res2 = xpcall(method, log.error, uri, ...) - tracy.ZoneEnd() - local passed = os.clock() - clock - if passed > 0.1 then - log.warn(('Call plugin event [%s] takes [%.3f] sec'):format(event, passed)) + local failed = 0 + local res1, res2 + for i, interface in ipairs(interfaces) do + local method = interface[event] + if type(method) ~= 'function' then + return false + end + local clock = os.clock() + tracy.ZoneBeginN('plugin dispatch:' .. event) + local suc + suc, res1, res2 = xpcall(method, log.error, uri, ...) + tracy.ZoneEnd() + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('Call plugin event [%s] takes [%.3f] sec'):format(event, passed)) + end + if not suc then + m.showError(scp, res1) + failed = failed + 1 + end end - if suc then - return true, res1, res2 - else - m.showError(scp, res1) + return failed == 0, res1, res2 +end + +function m.getVmPlugin(uri) + local scp = scope.getScope(uri) + ---@type pluginInterfaces + local interfaces = scp:get('pluginInterfaces') + if not interfaces then + return end - return false, res1 + return interfaces.VM end ---@async ---@param scp scope local function checkTrustLoad(scp) + if TRUST_ALL_PLUGINS then + return true + end local pluginPath = scp:get('pluginPath') local filePath = LOGPATH .. '/trusted' local trusted = util.loadFile(filePath) @@ -71,55 +100,114 @@ local function checkTrustLoad(scp) return true end +local function createMethodGroup(interfaces, key, methods) + local methodGroup = {} + + for method in pairs(methods) do + local funcs = setmetatable({}, { + __call = function (t, next, ...) + if #t == 0 then + return next(...) + else + local result + for _, fn in ipairs(t) do + result = fn(next, ...) + end + return result + end + end + }) + for _, interface in ipairs(interfaces) do + local func = interface[method] + if not func then + local namespace = interface[key] + if namespace then + func = namespace[method] + end + end + if func then + funcs[#funcs+1] = func + end + end + methodGroup[method] = funcs + end + return #methodGroup>0 and methodGroup or nil +end + ---@param uri uri local function initPlugin(uri) await.call(function () ---@async local scp = scope.getScope(uri) - local interface = {} - scp:set('pluginInterface', interface) + local interfaces = {} + scp:set('pluginInterfaces', interfaces) if not scp.uri then return end - - local pluginPath = ws.getAbsolutePath(scp.uri, config.get(scp.uri, 'Lua.runtime.plugin')) - log.info('plugin path:', pluginPath) - if not pluginPath then + ---@type string[]|string + local pluginConfigPaths = config.get(scp.uri, 'Lua.runtime.plugin') + if not pluginConfigPaths then return end - - --Adding the plugins path to package.path allows for requires in files - --to find files relative to itself. - local oldPath = package.path - local path = fs.path(pluginPath):parent_path() / '?.lua' - if not package.path:find(path:string(), 1, true) then - package.path = package.path .. ';' .. path:string() + local args = config.get(scp.uri, 'Lua.runtime.pluginArgs') + if args == nil then args = {} end + if type(pluginConfigPaths) == 'string' then + pluginConfigPaths = { pluginConfigPaths } end + for i, pluginConfigPath in ipairs(pluginConfigPaths) do + local myArgs = args + if args then + for k, v in pairs(args) do + if pluginConfigPath:find(k, 1, true) then + myArgs = v + break + end + end + end - local pluginLua = util.loadFile(pluginPath) - if not pluginLua then - log.warn('plugin not found:', pluginPath) - package.path = oldPath - return - end + local pluginPath = ws.getAbsolutePath(scp.uri, pluginConfigPath) + log.info('plugin path:', pluginPath) + if not pluginPath then + return + end - scp:set('pluginPath', pluginPath) + --Adding the plugins path to package.path allows for requires in files + --to find files relative to itself. + local oldPath = package.path + local path = fs.path(pluginPath):parent_path() / '?.lua' + if not package.path:find(path:string(), 1, true) then + package.path = package.path .. ';' .. path:string() + end - local env = setmetatable(interface, { __index = _ENV }) - local f, err = load(pluginLua, '@'..pluginPath, "t", env) - if not f then - log.error(err) - m.showError(scp, err) - return - end - if not client.isVSCode() and not checkTrustLoad(scp) then - return + local pluginLua = util.loadFile(pluginPath) + if not pluginLua then + log.warn('plugin not found:', pluginPath) + package.path = oldPath + return + end + + scp:set('pluginPath', pluginPath) + + local interface = setmetatable({}, { __index = _ENV }) + local f, err = load(pluginLua, '@' .. pluginPath, "t", interface) + if not f then + log.error(err) + m.showError(scp, err) + return + end + if not client.isVSCode() and not checkTrustLoad(scp) then + return + end + local suc, err = xpcall(f, log.error, f, uri, myArgs) + if not suc then + m.showError(scp, err) + return + end + interfaces[#interfaces+1] = interface end - local pluginArgs = config.get(scp.uri, 'Lua.runtime.pluginArgs') - local suc, err = xpcall(f, log.error, f, uri, pluginArgs) - if not suc then - m.showError(scp, err) - return + + for key, config in pairs(pluginConfigs) do + interfaces[key] = createMethodGroup(interfaces, key, config) end ws.resetFiles(scp) @@ -128,6 +216,7 @@ end ws.watch(function (ev, uri) if ev == 'startReload' then + require 'plugins' initPlugin(uri) end end) diff --git a/script/plugins/astHelper.lua b/script/plugins/astHelper.lua new file mode 100644 index 000000000..bfe2dd27b --- /dev/null +++ b/script/plugins/astHelper.lua @@ -0,0 +1,97 @@ +local luadoc = require 'parser.luadoc' +local ssub = require 'core.substring' +local guide = require 'parser.guide' +local _M = {} + +function _M.buildComment(t, value, pos) + return { + type = 'comment.short', + start = pos, + finish = pos, + text = "-@" .. t .. " " .. value, + virtual = true + } +end + +function _M.InsertDoc(ast, comm) + local comms = ast.state.comms or {} + comms[#comms+1] = comm + ast.state.comms = comms +end + +--- give the local/global variable add doc.class +---@param ast parser.object +---@param source parser.object local/global variable +---@param classname string +---@param group table? +function _M.addClassDoc(ast, source, classname, group) + return _M.addDoc(ast, source, "class", classname, group) +end + +--- give the local/global variable a luadoc comment +---@param ast parser.object +---@param source parser.object local/global variable +---@param key string +---@param value string +---@param group table? +function _M.addDoc(ast, source, key, value, group) + if source.type ~= 'local' and not guide.isGlobal(source) then + return false + end + local comment = _M.buildComment(key, value, source.start - 1) + local doc = luadoc.buildAndBindDoc(ast, source, comment, group) + if group then + group[#group+1] = doc + end + return doc +end + +---remove `ast` function node `index` arg, the variable will be the function local variable +---@param source parser.object function node +---@param index integer +---@return parser.object? +function _M.removeArg(source, index) + if source.type == 'function' or source.type == 'call' then + local arg = table.remove(source.args, index) + if not arg then + return nil + end + arg.parent = arg.parent.parent + return arg + end + return nil +end + +---ๆŠŠ็‰นๅฎšๅ‡ฝๆ•ฐๅฝ“ๆˆๆž„้€ ๅ‡ฝๆ•ฐ,`index` ๅ‚ๆ•ฐๆ˜ฏself +---@param classname string +---@param source parser.object function node +---@param index integer +---@return boolean, parser.object? +function _M.addClassDocAtParam(ast, classname, source, index) + local arg = _M.removeArg(source, index) + if arg then + return not not _M.addClassDoc(ast, arg, classname), arg + end + return false +end + +---ๆŠŠๅ‡ฝๆ•ฐๅ‚ๆ•ฐ็ป‘ๅฎš็ฑปๅž‹ +---@param ast parser.object +---@param typename string +---@param source parser.object +function _M.addParamTypeDoc(ast, typename, source) + if not guide.isParam(source) then + return false + end + local paramname = guide.getKeyName(source) + if not paramname then + return false + end + local comment = _M.buildComment("param", + ('%s %s'):format(paramname, typename), + source.start - 1) + + return luadoc.buildAndBindDoc(ast, source.parent.parent, comment) +end + +return _M diff --git a/script/plugins/ffi/c-parser/c99.lua b/script/plugins/ffi/c-parser/c99.lua new file mode 100644 index 000000000..9735afa29 --- /dev/null +++ b/script/plugins/ffi/c-parser/c99.lua @@ -0,0 +1,731 @@ +-- C99 grammar written in lpeg.re. +-- Adapted and translated from plain LPeg grammar for C99 +-- written by Wesley Smith https://github.com/Flymir/ceg +-- +-- Copyright (c) 2009 Wesley Smith +-- +-- 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. + +-- Reference used in the original and in this implementation: +-- http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1124.pdf + +local c99 = {} + +local re = require("parser.relabel") +local typed = require("plugins.ffi.c-parser.typed") + +local defs = {} + + +c99.tracing = false + +defs["trace"] = function(s, i) + if c99.tracing then + --local location = require("titan-compiler.location") + --local line, col = location.get_line_number(s, i) + --print("TRACE", line, col, "[[" ..s:sub(i, i+ 256):gsub("\n.*", "") .. "]]") + end + return true +end + +local typedefs = {} + +local function elem(xs, e) + for _, x in ipairs(xs) do + if e == x then + return true + end + end + return false +end + +defs["decl_func"] = typed("string, number, table -> boolean, Decl", function(_, _, decl) + typed.set_type(decl, "Decl") + return true, decl +end) + +defs["decl_ids"] = typed("string, number, table -> boolean, Decl?", function(_, _, decl) + -- store typedef + if elem(decl.spec, "typedef") then + if not (decl.ids and decl.ids[1] and decl.ids[1].decl) then + return true + end + for _, id in ipairs(decl.ids) do + local name = id.decl.name or id.decl.declarator.name + if name then + typedefs[name] = true + end + end + end + typed.set_type(decl, "Decl") + return true, decl +end) + +defs["is_typedef"] = function(_, _, id) + --print("is " .. id .. " a typedef? " .. tostring(not not typedefs[id])) + return typedefs[id], typedefs[id] and id +end + +defs["empty_table"] = function() + return true, {} +end + +-- Flatten nested expression tables +defs["nest_exp"] = typed("string, number, {Exp} -> boolean, Exp", function(_, _, exp) + typed.set_type(exp, "Exp") + if not exp.op then + return true, exp[1] + end + return true, exp +end) + +-- Primary expression tables +defs["prim_exp"] = typed("string, number, {string} -> boolean, Exp", function(_, _, exp) + typed.set_type(exp, "Exp") + return true, exp +end) + +-- Type tables +defs["type_exp"] = typed("string, number, table -> boolean, Exp", function(_, _, exp) + typed.check(exp[1], "Type") + typed.set_type(exp, "Exp") + return true, exp +end) + +-- Types +defs["type"] = typed("string, number, table -> boolean, Type", function(_, _, typ) + typed.set_type(typ, "Type") + return true, typ +end) + +defs["join"] = typed("string, number, {array} -> boolean, array", function(_, _, xss) + -- xss[1] .. xss[2] + if xss[2] then + table.move(xss[2], 1, #xss[2], #xss[1] + 1, xss[1]) + end + return true, xss[1] or {} +end) + +defs["postfix"] = typed("string, number, table -> boolean, table", function(_, _, pf) + typed.check(pf[1], "Exp") + if pf.postfix ~= "" then + pf[1].postfix = pf.postfix + end + return true, pf[1] +end) + +defs["litstruct"] = typed("string, number, number -> boolean, string", function(_, _, _) + return true, "litstruct" +end) + +--============================================================================== +-- Lexical Rules (used in both preprocessing and language processing) +--============================================================================== + +local lexical_rules = [[--lpeg.re + +TRACE <- ({} => trace) + +empty <- ("" => empty_table) + +-------------------------------------------------------------------------------- +-- Identifiers + +IDENTIFIER <- { identifierNondigit (identifierNondigit / [0-9])* } _ +identifierNondigit <- [a-zA-Z_] + / universalCharacterName + +identifierList <- {| IDENTIFIER ("," _ IDENTIFIER)* |} + +-------------------------------------------------------------------------------- +-- Universal Character Names + +universalCharacterName <- "\u" hexQuad + / "\U" hexQuad hexQuad +hexQuad <- hexDigit^4 + +-------------------------------------------------------------------------------- +-- String Literals + +STRING_LITERAL <- { ('"' / 'L"') sChar* '"' } _ + +sChar <- (!["\%nl] .) / escapeSequence + +-------------------------------------------------------------------------------- +-- Escape Sequences + +escapeSequence <- simpleEscapeSequence + / octalEscapeSequence + / hexEscapeSequence + / universalCharacterName + +simpleEscapeSequence <- "\" ['"?\abfnrtv] + +octalEscapeSequence <- "\" [0-7] [0-7]^-2 + +hexEscapeSequence <- "\x" hexDigit+ + +-------------------------------------------------------------------------------- +-- Constants + +INTEGER_CONSTANT <- { ( hexConstant integerSuffix? + / octalConstant integerSuffix? + / decimalConstant integerSuffix? + ) } _ + +decimalConstant <- [1-9] digit* +octalConstant <- "0" [0-7]* +hexConstant <- ("0x" / "0X") hexDigit+ + +digit <- [0-9] +hexDigit <- [0-9a-fA-F] + +integerSuffix <- unsignedSuffix longLongSuffix + / unsignedSuffix longSuffix? + / longLongSuffix unsignedSuffix? + / longSuffix unsignedSuffix? + +unsignedSuffix <- [uU] +longSuffix <- [lL] +longLongSuffix <- "ll" / "LL" + +FLOATING_CONSTANT <- { ( decimalFloatingConstant + / hexFloatingConstant + ) } _ + +decimalFloatingConstant <- fractionalConstant exponentPart? floatingSuffix? + / digit+ exponentPart floatingSuffix? + +hexFloatingConstant <- ("0x" / "0X" ) ( hexFractionalConstant binaryExponentPart floatingSuffix? + / hexDigit+ binaryExponentPart floatingSuffix? ) + +fractionalConstant <- digit* "." digit+ + / digit "." + +exponentPart <- [eE] [-+]? digit+ + +hexFractionalConstant <- hexDigit+? "." hexDigit+ + / hexDigit+ "." + +binaryExponentPart <- [pP] digit+ + +floatingSuffix <- [flFL] + +CHARACTER_CONSTANT <- { ("'" / "L'") cChar+ "'" } _ + +cChar <- (!['\%nl] .) / escapeSequence + +enumerationConstant <- IDENTIFIER + +]] + +local common_expression_rules = [[--lpeg.re + +-------------------------------------------------------------------------------- +-- Common Expression Rules + +multiplicativeExpression <- {| castExpression ({:op: [*/%] :} _ castExpression )* |} => nest_exp +additiveExpression <- {| multiplicativeExpression ({:op: [-+] :} _ multiplicativeExpression )* |} => nest_exp +shiftExpression <- {| additiveExpression ({:op: ("<<" / ">>") :} _ additiveExpression )* |} => nest_exp +relationalExpression <- {| shiftExpression ({:op: (">=" / "<=" / "<" / ">") :} _ shiftExpression )* |} => nest_exp +equalityExpression <- {| relationalExpression ({:op: ("==" / "!=") :} _ relationalExpression )* |} => nest_exp +bandExpression <- {| equalityExpression ({:op: "&" :} _ equalityExpression )* |} => nest_exp +bxorExpression <- {| bandExpression ({:op: "^" :} _ bandExpression )* |} => nest_exp +borExpression <- {| bxorExpression ({:op: "|" :} _ bxorExpression )* |} => nest_exp +andExpression <- {| borExpression ({:op: "&&" :} _ borExpression )* |} => nest_exp +orExpression <- {| andExpression ({:op: "||" :} _ andExpression )* |} => nest_exp +conditionalExpression <- {| orExpression ({:op: "?" :} _ expression ":" _ conditionalExpression)? |} => nest_exp + +constantExpression <- conditionalExpression + +]] + +--============================================================================== +-- Language Rules (Phrase Structure Grammar) +--============================================================================== + +local language_rules = [[--lpeg.re + +-------------------------------------------------------------------------------- +-- External Definitions + +translationUnit <- %s* {| externalDeclaration* |} "$EOF$" + +externalDeclaration <- functionDefinition + / declaration + +functionDefinition <- {| {:spec: {| declarationSpecifier+ |} :} {:func: declarator :} {:decls: declaration* :} {:code: compoundStatement :} |} => decl_func + +-------------------------------------------------------------------------------- +-- Declarations + +declaration <- {| gccExtensionSpecifier? {:spec: {| declarationSpecifier+ |} :} ({:ids: initDeclarationList :})? gccExtensionSpecifier* ";" _ |} => decl_ids + +declarationSpecifier <- storageClassSpecifier + / typeSpecifier + / typeQualifier + / functionSpecifier + +initDeclarationList <- {| initDeclarator ("," _ initDeclarator)* |} + +initDeclarator <- {| {:decl: declarator :} ("=" _ {:value: initializer :} )? |} + +gccExtensionSpecifier <- "__attribute__" _ "(" _ "(" _ gccAttributeList ")" _ ")" _ + / gccAsm + / clangAsm + / "__DARWIN_ALIAS_STARTING_MAC_1060" _ "(" _ clangAsm ")" _ + / "__AVAILABILITY_INTERNAL" [a-zA-Z0-9_]+ _ ("(" _ STRING_LITERAL ")" _ )? + +gccAsm <- "__asm__" _ "(" _ (STRING_LITERAL / ":" _ / expression)+ ")" _ + +clangAsm <- "__asm" _ "(" _ (STRING_LITERAL / ":" _ / expression)+ ")" _ + +gccAttributeList <- {| gccAttributeItem ("," _ gccAttributeItem )* |} + +gccAttributeItem <- clangAsm + / IDENTIFIER ("(" _ (expression ("," _ expression)*)? ")" _)? + / "" + +storageClassSpecifier <- { "typedef" } _ + / { "extern" } _ + / { "static" } _ + / { "auto" } _ + / { "register" } _ + +typeSpecifier <- typedefName + / { "void" } _ + / { "bool" } _ + / { "char" } _ + / { "short" } _ + / { "int" } _ + / { "long" } _ + / { "float" } _ + / { "double" } _ + / { "signed" } _ + / { "__signed" } _ + / { "__signed__" } _ + / { "unsigned" } _ + / { "ptrdiff_t" } _ + / { "size_t" } _ + / { "ssize_t" } _ + / { "wchar_t" } _ + / { "int8_t" } _ + / { "int16_t" } _ + / { "int32_t" } _ + / { "int64_t" } _ + / { "uint8_t" } _ + / { "uint16_t" } _ + / { "uint32_t" } _ + / { "uint64_t" } _ + / { "intptr_t" } _ + / { "uintptr_t" } _ + / { "__int8" } _ + / { "__int16" } _ + / { "__int32" } _ + / { "__int64" } _ + / { "_Bool" } _ + / { "_Complex" } _ + / { "complex" } _ + / { "__complex" } _ + / { "__complex__" } _ + / { "__ptr32" } _ + / { "__ptr64" } _ + / structOrUnionSpecifier + / enumSpecifier + +typeQualifier <- { "const" } _ + / { "restrict" } _ + / { "volatile" } _ + +functionSpecifier <- { "inline" } _ + +structOrUnionSpecifier <- {| {:type: structOrUnion :} ({:id: IDENTIFIER :})? "{" _ {:fields: {| structDeclaration+ |} :}? "}" _ |} + / {| {:type: structOrUnion :} {:id: IDENTIFIER :} |} + +structOrUnion <- { "struct" } _ + / { "union" } _ + +anonymousUnion <- {| {:type: {| {:type: { "union" } :} _ "{" _ {:fields: {| structDeclaration+ |} :}? "}" _ |} :} |} ";" _ + +structDeclaration <- anonymousUnion + / {| {:type: {| specifierQualifier+ |} :} {:ids: structDeclaratorList :} |} ";" _ + +specifierQualifier <- typeSpecifier + / typeQualifier + +structDeclaratorList <- {| structDeclarator ("," _ structDeclarator)* |} + +structDeclarator <- declarator? ":" _ constantExpression + / declarator + +enumSpecifier <- {| {:type: enum :} ({:id: IDENTIFIER :})? "{" _ {:values: enumeratorList :}? ("," _)? "}" _ |} + / {| {:type: enum :} {:id: IDENTIFIER :} |} + +enum <- { "enum" } _ + +enumeratorList <- {| enumerator ("," _ enumerator)* |} + +enumerator <- {| {:id: enumerationConstant :} ("=" _ {:value: constantExpression :})? |} + +declarator <- {| pointer? directDeclarator |} + +directDeclarator <- {:name: IDENTIFIER :} ddRec + / "(" _ {:declarator: declarator :} ")" _ ddRec +ddRec <- "[" _ {| {:idx: typeQualifier* assignmentExpression? :} |} "]" _ ddRec + / "[" _ {| {:idx: { "static" } _ typeQualifier* assignmentExpression :} |} "]" _ ddRec + / "[" _ {| {:idx: typeQualifier+ { "static" } _ assignmentExpression :} |} "]" _ ddRec + / "[" _ {| {:idx: typeQualifier* { "*" } _ :} |} "]" _ ddRec + / "(" _ {:params: parameterTypeList / empty :} ")" _ ddRec + / "(" _ {:params: identifierList / empty :} ")" _ ddRec + / "" + +pointer <- {| ({ "*"/"^" } _ typeQualifier*)+ |} + +parameterTypeList <- {| parameterList "," _ {| { "..." } |} _ |} => join + / parameterList + +parameterList <- {| parameterDeclaration ("," _ parameterDeclaration)* |} + +parameterDeclaration <- {| {:param: {| {:type: {| declarationSpecifier+ |} :} {:id: (declarator / abstractDeclarator?) :} |} :} |} + +typeName <- {| specifierQualifier+ abstractDeclarator? |} => type + +abstractDeclarator <- pointer? directAbstractDeclarator + / pointer + +directAbstractDeclarator <- ("(" _ abstractDeclarator ")" _) directAbstractDeclarator2* + / directAbstractDeclarator2+ +directAbstractDeclarator2 <- "[" _ assignmentExpression? "]" _ + / "[" _ "*" _ "]" _ + / "(" _ (parameterTypeList / empty) ")" _ + +typedefName <- IDENTIFIER => is_typedef + +initializer <- assignmentExpression + / "{" _ initializerList ("," _)? "}" _ + +initializerList <- {| initializerList2 ("," _ initializerList2)* |} +initializerList2 <- designation? initializer + +designation <- designator+ "=" _ + +designator <- "[" _ constantExpression "]" _ + / "." _ IDENTIFIER + +-------------------------------------------------------------------------------- +-- Statements + +statement <- labeledStatement + / compoundStatement + / expressionStatement + / selectionStatement + / iterationStatement + / jumpStatement + / gccAsm ";" _ + +labeledStatement <- IDENTIFIER ":" _ statement + / "case" _ constantExpression ":" _ statement + / "default" _ ":" _ statement + +compoundStatement <- "{" _ blockItem+ "}" _ + +blockItem <- declaration + / statement + +expressionStatement <- expression? ";" _ + +selectionStatement <- "if" _ "(" _ expression ")" _ statement "else" _ statement + / "if" _ "(" _ expression ")" _ statement + / "switch" _ "(" _ expression ")" _ statement + +iterationStatement <- "while" _ "(" _ expression ")" _ statement + / "do" _ statement "while" _ "(" _ expression ")" _ ";" _ + / "for" _ "(" _ expression? ";" _ expression? ";" _ expression? ")" _ statement + / "for" _ "(" _ declaration expression? ";" _ expression? ")" _ statement + +jumpStatement <- "goto" _ IDENTIFIER ";" _ + / "continue" _ ";" _ + / "break" _ ";" _ + / "return" _ expression? ";" _ + +-------------------------------------------------------------------------------- +-- Advanced Language Expression Rules +-- (which require type names) + +postfixExpression <- {| {:op: {} => litstruct :} "(" _ {:struct: typeName :} ")" _ "{" _ {:vals: initializerList :} ("," _)? "}" _ peRec |} => nest_exp + / {| primaryExpression {:postfix: peRec :} |} => postfix + +sizeofOrPostfixExpression <- {| {:op: "sizeof" :} _ "(" _ typeName ")" _ |} => type_exp + / {| {:op: "sizeof" :} _ unaryExpression |} => nest_exp + / postfixExpression + +castExpression <- {| "(" _ typeName ")" _ castExpression |} => type_exp + / unaryExpression + +]] + +--============================================================================== +-- Language Expression Rules +--============================================================================== + +local language_expression_rules = [[--lpeg.re + +-------------------------------------------------------------------------------- +-- Language Expression Rules +-- (rules which differ from preprocessing stage) + +expression <- {| assignmentExpression ({:op: "," :} _ assignmentExpression)* |} => nest_exp + +constant <- ( FLOATING_CONSTANT + / INTEGER_CONSTANT + / CHARACTER_CONSTANT + / enumerationConstant + ) + +primaryExpression <- {| constant |} => prim_exp + / {| IDENTIFIER |} => prim_exp + / {| STRING_LITERAL+ |} => prim_exp + / "(" _ expression ")" _ + +peRec <- {| "[" _ {:idx: expression :} "]" _ peRec |} + / {| "(" _ {:args: argumentExpressionList / empty :} ")" _ peRec |} + / {| "." _ {:dot: IDENTIFIER :} peRec |} + / {| "->" _ {:arrow: IDENTIFIER :} peRec |} + / {| "++" _ peRec |} + / {| "--" _ peRec |} + / "" + +argumentExpressionList <- {| assignmentExpression ("," _ assignmentExpression)* |} + +unaryExpression <- {| {:op: prefixOp :} unaryExpression |} => nest_exp + / {| {:op: unaryOperator :} castExpression |} => nest_exp + / sizeofOrPostfixExpression + +prefixOp <- { "++" } _ + / { "--" } _ + +unaryOperator <- { [-+~!*&] } _ + +assignmentExpression <- unaryExpression assignmentOperator assignmentExpression + / conditionalExpression + +assignmentOperator <- "=" _ + / "*=" _ + / "/=" _ + / "%=" _ + / "+=" _ + / "-=" _ + / "<<=" _ + / ">>=" _ + / "&=" _ + / "^=" _ + / "|=" _ + +-------------------------------------------------------------------------------- +-- Language whitespace + +_ <- %s+ +S <- %s+ + +]] + +local simplified_language_expression_rules = [[--lpeg.re + +-------------------------------------------------------------------------------- +-- Simplified Language Expression Rules +-- (versions that do not require knowledge of type names) + +postfixExpression <- {| primaryExpression {:postfix: peRec :} |} => postfix + +sizeofOrPostfixExpression <- postfixExpression + +castExpression <- unaryExpression + +]] + +--============================================================================== +-- Preprocessing Rules +--============================================================================== + +local preprocessing_rules = [[--lpeg.re + +preprocessingLine <- _ ( "#" _ directive _ + / "#" _ preprocessingTokenList? {| _ |} -- non-directive, ignore + / preprocessingTokenList + / empty + ) + +preprocessingTokenList <- {| (preprocessingToken _)+ |} + +directive <- {| {:directive: "if" :} S {:exp: preprocessingTokenList :} |} + / {| {:directive: "ifdef" :} S {:id: IDENTIFIER :} |} + / {| {:directive: "ifndef" :} S {:id: IDENTIFIER :} |} + / {| {:directive: "elif" :} S {:exp: preprocessingTokenList :} |} + / {| {:directive: "else" :} |} + / {| {:directive: "endif" :} |} + / {| {:directive: "include" :} S {:exp: headerName :} |} + / {| {:directive: "define" :} S {:id: IDENTIFIER :} "(" _ {:args: defineArgList :} _ ")" _ {:repl: replacementList :} |} + / {| {:directive: "define" :} S {:id: IDENTIFIER :} _ {:repl: replacementList :} |} + / {| {:directive: "undef" :} S {:id: IDENTIFIER :} |} + / {| {:directive: "line" :} S {:line: preprocessingTokenList :} |} + / {| {:directive: "error" :} S {:error: preprocessingTokenList / empty :} |} + / {| {:directive: "error" :} |} + / {| {:directive: "pragma" :} S {:pragma: preprocessingTokenList / empty :} |} + / gccDirective + / "" + +gccDirective <- {| {:directive: "include_next" :} S {:exp: headerName :} |} + / {| {:directive: "warning" :} S {:exp: preprocessingTokenList / empty :} |} + +defineArgList <- {| { "..." } |} + / {| identifierList _ "," _ {| { "..." } |} |} => join + / identifierList + / empty + +replacementList <- {| (preprocessingToken _)* |} + +preprocessingToken <- preprocessingNumber + / CHARACTER_CONSTANT + / STRING_LITERAL + / punctuator + / IDENTIFIER + +headerName <- {| {:mode: "<" -> "system" :} { (![%nl>] .)+ } ">" |} + / {| {:mode: '"' -> "quote" :} { (![%nl"] .)+ } '"' |} + / {| IDENTIFIER |} -- macro + +preprocessingNumber <- { ("."? digit) ( digit + / [eEpP] [-+] + / identifierNondigit + / "." + )* } + +punctuator <- { digraphs / '...' / '<<=' / '>>=' / + '##' / '<<' / '>>' / '->' / '++' / '--' / '&&' / '||' / '<=' / '>=' / + '==' / '!=' / '*=' / '/=' / '%=' / '+=' / '-=' / '&=' / '^=' / '|=' / + '#' / '[' / ']' / '(' / ')' / '{' / '}' / '.' / '&' / + '*' / '+' / '-' / '~' / '!' / '/' / '%' / '<' / '>' / + '^' / '|' / '?' / ':' / ';' / ',' / '=' } + +digraphs <- '%:%:' -> "##" + / '%:' -> "#" + / '<:' -> "[" + / ':>' -> "]" + / '<%' -> "{" + / '%>' -> "}" + +-------------------------------------------------------------------------------- +-- Preprocessing whitespace + +_ <- %s* +S <- %s+ + +]] + +--============================================================================== +-- Preprocessing Expression Rules +--============================================================================== + +local preprocessing_expression_rules = [[--lpeg.re + +-------------------------------------------------------------------------------- +-- Preprocessing Expression Rules +-- (rules which differ from language processing stage) + +expression <- constantExpression + +constant <- FLOATING_CONSTANT + / INTEGER_CONSTANT + / CHARACTER_CONSTANT + +primaryExpression <- {| IDENTIFIER |} => prim_exp + / {| constant |} => prim_exp + / "(" _ expression _ ")" _ + +postfixExpression <- primaryExpression peRec +peRec <- "(" _ (argumentExpressionList / empty) ")" _ peRec + / "" + +argumentExpressionList <- {| expression ("," _ expression )* |} + +unaryExpression <- {| {:op: unaryOperator :} unaryExpression |} => nest_exp + / primaryExpression + +unaryOperator <- { [-+~!] } _ + / { "defined" } _ + +castExpression <- unaryExpression + +-------------------------------------------------------------------------------- +-- Preprocessing expression whitespace + +_ <- %s* +S <- %s+ + +]] + +local preprocessing_grammar = re.compile( + preprocessing_rules .. + lexical_rules, defs) + +local preprocessing_expression_grammar = re.compile( + preprocessing_expression_rules .. + lexical_rules .. + common_expression_rules, defs) + +local language_expression_grammar = re.compile( + language_expression_rules .. + simplified_language_expression_rules .. + lexical_rules .. + common_expression_rules, defs) + +local language_grammar = re.compile( + language_rules .. + language_expression_rules .. + lexical_rules .. + common_expression_rules, defs) + +local function match(grammar, subject) + local res, err, pos = grammar:match(subject) + if res == nil then + local l, c = re.calcline(subject, pos) + local fragment = subject:sub(pos, pos+20) + return res, err, l, c, fragment + end + return res +end + +function c99.match_language_grammar(subject) + typedefs = {} + return match(language_grammar, subject) +end + +function c99.match_language_expression_grammar(subject) + return match(language_expression_grammar, subject) +end + +function c99.match_preprocessing_grammar(subject) + return match(preprocessing_grammar, subject) +end + +function c99.match_preprocessing_expression_grammar(subject) + return match(preprocessing_expression_grammar, subject) +end + +return c99 diff --git a/script/plugins/ffi/c-parser/cdefines.lua b/script/plugins/ffi/c-parser/cdefines.lua new file mode 100644 index 000000000..55065f2d2 --- /dev/null +++ b/script/plugins/ffi/c-parser/cdefines.lua @@ -0,0 +1,152 @@ + +local cdefines = {} + +local c99 = require("plugins.ffi.c-parser.c99") +local cpp = require("plugins.ffi.c-parser.cpp") +local typed = require("plugins.ffi.c-parser.typed") + +local function add_type(lst, name, typ) + lst[name] = typ + table.insert(lst, { name = name, type = typ }) +end + +local base_c_types = { + CONST_CHAR_PTR = { "const", "char", "*" }, + CONST_CHAR = { "const", "char" }, + LONG_LONG = { "long", "long" }, + LONG = { "long" }, + DOUBLE = { "double" }, + INT = { "int" }, +} + +local function get_binop_type(e1, e2) + if e1[1] == "double" or e2[1] == "double" then + return base_c_types.DOUBLE + end + if e1[2] == "long" or e2[2] == "long" then + return base_c_types.LONG_LONG + end + if e1[1] == "long" or e2[1] == "long" then + return base_c_types.LONG + end + return base_c_types.INT +end + +local binop_set = { + ["+"] = true, + ["-"] = true, + ["*"] = true, + ["/"] = true, + ["%"] = true, +} + +local relop_set = { + ["<"] = true, + [">"] = true, + [">="] = true, + ["<="] = true, + ["=="] = true, + ["!="] = true, +} + +local bitop_set = { + ["<<"] = true, + [">>"] = true, + ["&"] = true, + ["^"] = true, + ["|"] = true, +} + +-- Best-effort assessment of the type of a #define +local get_type_of_exp +get_type_of_exp = typed("Exp, TypeList -> {string}?", function(exp, lst) + if type(exp[1]) == "string" and exp[2] == nil then + local val = exp[1] + if val:sub(1,1) == '"' or val:sub(1,2) == 'L"' then + return base_c_types.CONST_CHAR_PTR + elseif val:sub(1,1) == "'" or val:sub(1,2) == "L'" then + return base_c_types.CONST_CHAR + elseif val:match("^[0-9]*LL$") then + return base_c_types.LONG_LONG + elseif val:match("^[0-9]*L$") then + return base_c_types.LONG + elseif val:match("%.") then + return base_c_types.DOUBLE + else + return base_c_types.INT + end + end + + if type(exp[1]) == "string" and exp[2] and exp[2].args then + local fn = lst[exp[1]] + if not fn or not fn.ret then + return nil -- unknown function, or not a function + end + local r = fn.ret.type + return table.move(r, 1, #r, 1, {}) -- shallow_copy(r) + end + + if exp.unop == "*" then + local etype = get_type_of_exp(exp[1], lst) + if not etype then + return nil + end + local rem = table.remove(etype) + assert(rem == "*") + return etype + elseif exp.unop == "-" then + return get_type_of_exp(exp[1], lst) + elseif exp.op == "?" then + return get_type_of_exp(exp[2], lst) + elseif exp.op == "," then + return get_type_of_exp(exp[#exp], lst) + elseif binop_set[exp.op] then + local e1 = get_type_of_exp(exp[1], lst) + if not e1 then + return nil + end + -- some binops are also unops (e.g. - and *) + if exp[2] then + local e2 = get_type_of_exp(exp[2], lst) + if not e2 then + return nil + end + return get_binop_type(e1, e2) + else + return e1 + end + elseif relop_set[exp.op] then + return base_c_types.INT + elseif bitop_set[exp.op] then + return get_type_of_exp(exp[1], lst) -- ...or should it be int? + elseif exp.op then + print("FIXME unsupported op", exp.op) + end + return nil +end) + +function cdefines.register_define(lst, name, text, define_set) + local exp, err, line, col = c99.match_language_expression_grammar(text .. " ") + if not exp then + -- failed parsing expression + -- print(("failed parsing: %d:%d: %s\n"):format(line, col, text)) + return + end + local typ = get_type_of_exp(exp, lst) + if typ then + add_type(lst, name, { type = typ }) + end +end + +function cdefines.register_defines(lst, define_set) + for name, def in pairs(define_set) do + if #def == 0 then + goto continue + end + local text = cpp.expand_macro(name, define_set) + cdefines.register_define(lst, name, text, define_set) + ::continue:: + end +end + +return cdefines diff --git a/script/plugins/ffi/c-parser/cdriver.lua b/script/plugins/ffi/c-parser/cdriver.lua new file mode 100644 index 000000000..ab48d01a6 --- /dev/null +++ b/script/plugins/ffi/c-parser/cdriver.lua @@ -0,0 +1,54 @@ +local cdriver = {} + +local cpp = require("plugins.ffi.c-parser.cpp") +local c99 = require("plugins.ffi.c-parser.c99") +local ctypes = require("plugins.ffi.c-parser.ctypes") +local cdefines = require("plugins.ffi.c-parser.cdefines") + +function cdriver.process_file(filename) + local ctx, err = cpp.parse_file(filename) + if not ctx then + return nil, "failed preprocessing '"..filename.."': " .. err + end + + local srccode = table.concat(ctx.output, "\n").." $EOF$" + + local res, err, line, col, fragment = c99.match_language_grammar(srccode) + if not res then + return nil, ("failed parsing: %s:%d:%d: %s\n%s"):format(filename, line, col, err, fragment) + end + + local ffi_types, err = ctypes.register_types(res) + if not ffi_types then + return nil, err + end + + cdefines.register_defines(ffi_types, ctx.defines) + + return ffi_types +end + +function cdriver.process_context(context) + local ctx, err = cpp.parse_context(context) + if not ctx then + return nil, "failed preprocessing '"..context.."': " .. err + end + + local srccode = table.concat(ctx.output, "\n").." $EOF$" + + local res, err, line, col, fragment = c99.match_language_grammar(srccode) + if not res then + return nil, ("failed parsing: %s:%d:%d: %s\n%s"):format(context, line, col, err, fragment) + end + + local ffi_types, err = ctypes.register_types(res) + if not ffi_types then + return nil, err + end + + cdefines.register_defines(ffi_types, ctx.defines) + + return ffi_types +end + +return cdriver diff --git a/script/plugins/ffi/c-parser/cpp.lua b/script/plugins/ffi/c-parser/cpp.lua new file mode 100644 index 000000000..eaa343305 --- /dev/null +++ b/script/plugins/ffi/c-parser/cpp.lua @@ -0,0 +1,869 @@ +local cpp = {} + +local typed = require("plugins.ffi.c-parser.typed") +local c99 = require("plugins.ffi.c-parser.c99") + +local SEP = package.config:sub(1,1) + +local function shl(a, b) + return a << b +end +local function shr(a, b) + return a >> b +end + +local function debug(...) end +--[[ +local inspect = require("inspect") +local function debug(...) + local args = { ... } + for i, arg in ipairs(args) do + if type(arg) == "table" then + args[i] = inspect(arg) + end + end + print(table.unpack(args)) +end + +local function is_sequence(xs) + if type(xs) ~= "table" then + return false + end + local l = #xs + for k, _ in pairs(xs) do + if type(k) ~= "number" or k < 1 or k > l or math.floor(k) ~= k then + return false + end + end + return true +end +--]] + +local gcc_default_defines +do + local default_defines + + local function shallow_copy(t) + local u = {} + for k,v in pairs(t) do + u[k] = v + end + return u + end + + gcc_default_defines = function() + if default_defines then + return shallow_copy(default_defines) + end + + local pd = io.popen("LANG=C gcc -dM -E - < /dev/null") + if not pd then + return {} + end + local blank_ctx = { + incdirs = {}, + defines = {}, + ifmode = { true }, + output = {}, + current_dir = {}, + } + typed.set_type(blank_ctx, "Ctx") + local ctx = cpp.parse_file("-", pd, blank_ctx) + + ctx.defines["__builtin_va_list"] = { "char", "*" } + ctx.defines["__extension__"] = {} + ctx.defines["__attribute__"] = { args = { "arg" }, repl = {} } + ctx.defines["__restrict__"] = { "restrict" } + ctx.defines["__restrict"] = { "restrict" } + ctx.defines["__inline__"] = { "inline" } + ctx.defines["__inline"] = { "inline" } + + default_defines = ctx.defines + return shallow_copy(ctx.defines) + end +end + +local function cpp_include_paths() + local pd = io.popen("LANG=C cpp -v /dev/null -o /dev/null 2>&1") + if not pd then + return { quote = {}, system = { "/usr/include"} } + end + local res = { + quote = {}, + system = {}, + } + local mode = nil + for line in pd:lines() do + if line:find([[#include "..." search starts here]], 1, true) then + mode = "quote" + elseif line:find([[#include <...> search starts here]], 1, true) then + mode = "system" + elseif line:find([[End of search list]], 1, true) then + mode = nil + elseif mode then + table.insert(res[mode], line:sub(2)) + end + end + pd:close() + return res +end + +-- TODO default defines: `gcc -dM -E - < /dev/null` + +-- Not supported: +-- * character set conversion +-- * trigraphs + +local states = { + any = { + ['"'] = { next = "dquote" }, + ["'"] = { next = "squote" }, + ["/"] = { silent = true, next = "slash" }, + }, + dquote = { + ['"'] = { next = "any" }, + ["\\"] = { next = "dquote_backslash" }, + }, + dquote_backslash = { + single_char = true, + default = { next = "dquote" }, + }, + squote = { + ["'"] = { next = "any" }, + ["\\"] = { next = "squote_backslash" }, + }, + squote_backslash = { + single_char = true, + default = { next = "squote" }, + }, + slash = { + single_char = true, + ["/"] = { add = " ", silent = true, next = "line_comment" }, + ["*"] = { add = " ", silent = true, next = "block_comment" }, + default = { add = "/", next = "any" }, + }, + line_comment = { + silent = true, + }, + block_comment = { + silent = true, + ["*"] = { silent = true, next = "try_end_block_comment" }, + continue_line = "block_comment", + }, + try_end_block_comment = { + single_char = true, + silent = true, + ["/"] = { silent = true, next = "any" }, + ["*"] = { silent = true, next = "try_end_block_comment" }, + default = { silent = true, next = "block_comment" }, + continue_line = "block_comment", + }, +} + +for _, rules in pairs(states) do + local out = "[" + for k, _ in pairs(rules) do + if #k == 1 then + out = out .. k + end + end + out = out .. "]" + rules.pattern = out ~= "[]" and out +end + +local function add(buf, txt) + if not buf then + buf = {} + end + table.insert(buf, txt) + return buf +end + +cpp.initial_processing = typed("FILE* -> LineList", function(fd) + local backslash_buf + local buf + local state = "any" + local output = {} + local linenr = 0 + for line in fd:lines() do + linenr = linenr + 1 + local len = #line + if line:find("\\", len, true) then + -- If backslash-terminated, buffer it + backslash_buf = add(backslash_buf, line:sub(1, len - 1)) + else + -- Merge backslash-terminated line + if backslash_buf then + table.insert(backslash_buf, line) + line = table.concat(backslash_buf) + end + backslash_buf = nil + + len = #line + local i = 1 + local out = "" + -- Go through the line + while i <= len do + -- Current state in the state machine + local st = states[state] + + -- Look for next character matching a state transition + local n = nil + if st.pattern then + if st.single_char then + if line:sub(i,i):find(st.pattern) then + n = i + end + else + n = line:find(st.pattern, i) + end + end + + local transition, ch + if n then + ch = line:sub(n, n) + transition = st[ch] + else + n = i + ch = line:sub(n, n) + transition = st.default + end + + if not transition then + -- output the rest of the string if we should + if not st.silent then + out = i == 1 and line or line:sub(i) + end + break + end + + -- output everything up to the transition if we should + if n > i and not st.silent then + buf = add(buf, line:sub(i, n - 1)) + end + + -- Some transitions output an explicit character + if transition.add then + buf = add(buf, transition.add) + end + + if not transition.silent then + buf = add(buf, ch) + end + + -- and move to the next state + state = transition.next + i = n + 1 + end + + -- If we ended in a non-line-terminating state + if states[state].continue_line then + -- buffer the output and keep going + buf = add(buf, out) + state = states[state].continue_line + else + -- otherwise, flush the buffer + if buf then + table.insert(buf, out) + out = table.concat(buf) + buf = nil + end + -- output the string and reset the state. + table.insert(output, { nr = linenr, line = out}) + state = "any" + end + end + end + fd:close() + typed.set_type(output, "LineList") + return output +end) + +cpp.tokenize = typed("string -> table", function(line) + return c99.match_preprocessing_grammar(line) +end) + +local function find_file(ctx, filename, mode, is_next) + local paths = {} + local current_dir = ctx.current_dir[#ctx.current_dir] + if mode == "quote" or is_next then + if not is_next then + table.insert(paths, current_dir) + end + for _, incdir in ipairs(ctx.incdirs.quote or {}) do + table.insert(paths, incdir) + end + end + if mode == "system" or is_next then + for _, incdir in ipairs(ctx.incdirs.system or {}) do + table.insert(paths, incdir) + end + end + if is_next then + while paths[1] and paths[1] ~= current_dir do + table.remove(paths, 1) + end + table.remove(paths, 1) + end + for _, path in ipairs(paths) do + local pathname = path..SEP..filename + local fd, err = io.open(pathname, "r") + if fd then + return pathname, fd + end + end + return nil, nil, "file not found" +end + +local parse_expression = typed("{string} -> Exp?", function(tokens) + local text = table.concat(tokens, " ") + local exp, err, _, _, fragment = c99.match_preprocessing_expression_grammar(text) + if not exp then + print("Error parsing expression: " .. tostring(err) .. ": " .. text .. " AT " .. fragment) + end + return exp +end) + +local eval_exp +eval_exp = typed("Ctx, Exp -> number", function(ctx, exp) + debug(exp) + + if not exp.op then + local val = exp[1] + typed.check(val, "string") + local defined = ctx.defines[val] + if defined then + assert(type(defined) == "table") + local subexp = parse_expression(defined) + if not subexp then + return 0 -- FIXME + end + return eval_exp(ctx, subexp) + end + val = val:gsub("U*L*$", "") + if val:match("^0[xX]") then + return tonumber(val) or 0 + elseif val:sub(1,1) == "0" then + return tonumber(val, 8) or 0 + else + return tonumber(val) or 0 + end + elseif exp.op == "+" then + if exp[2] then + return eval_exp(ctx, exp[1]) + eval_exp(ctx, exp[2]) + else + return eval_exp(ctx, exp[1]) + end + elseif exp.op == "-" then + if exp[2] then + return eval_exp(ctx, exp[1]) - eval_exp(ctx, exp[2]) + else + return -(eval_exp(ctx, exp[1])) + end + elseif exp.op == "*" then return eval_exp(ctx, exp[1]) * eval_exp(ctx, exp[2]) + elseif exp.op == "/" then return eval_exp(ctx, exp[1]) / eval_exp(ctx, exp[2]) + elseif exp.op == ">>" then return shr(eval_exp(ctx, exp[1]), eval_exp(ctx, exp[2])) -- FIXME C semantics + elseif exp.op == "<<" then return shl(eval_exp(ctx, exp[1]), eval_exp(ctx, exp[2])) -- FIXME C semantics + elseif exp.op == "==" then return (eval_exp(ctx, exp[1]) == eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == "!=" then return (eval_exp(ctx, exp[1]) ~= eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == ">=" then return (eval_exp(ctx, exp[1]) >= eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == "<=" then return (eval_exp(ctx, exp[1]) <= eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == ">" then return (eval_exp(ctx, exp[1]) > eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == "<" then return (eval_exp(ctx, exp[1]) < eval_exp(ctx, exp[2])) and 1 or 0 + elseif exp.op == "!" then return (eval_exp(ctx, exp[1]) == 1) and 0 or 1 + elseif exp.op == "&&" then + for _, e in ipairs(exp) do + if eval_exp(ctx, e) == 0 then + return 0 + end + end + return 1 + elseif exp.op == "||" then + for _, e in ipairs(exp) do + if eval_exp(ctx, e) ~= 0 then + return 1 + end + end + return 0 + elseif exp.op == "?" then + if eval_exp(ctx, exp[1]) ~= 0 then + return eval_exp(ctx, exp[2]) + else + return eval_exp(ctx, exp[3]) + end + elseif exp.op == "defined" then + return (ctx.defines[exp[1][1]] ~= nil) and 1 or 0 + else + error("unimplemented operator " .. tostring(exp.op)) + end +end) + +local consume_parentheses = typed("{string}, number, LineList, number -> {{string}}, number", function(tokens, start, linelist, cur) + local args = {} + local i = start + 1 + local arg = {} + local stack = 0 + while true do + local token = tokens[i] + if token == nil then + repeat + cur = cur + 1 + if not linelist[cur] then + error("unterminated function-like macro") + end + local nextline = linelist[cur].tk + linelist[cur].tk = {} + table.move(nextline, 1, #nextline, i, tokens) + token = tokens[i] + until token + end + if token == "(" then + stack = stack + 1 + table.insert(arg, token) + elseif token == ")" then + if stack == 0 then + if #arg > 0 then + table.insert(args, arg) + end + break + end + stack = stack - 1 + table.insert(arg, token) + elseif token == "," then + if stack == 0 then + table.insert(args, arg) + arg = {} + else + table.insert(arg, token) + end + else + table.insert(arg, token) + end + i = i + 1 + end + return args, i +end) + +local function array_copy(t) + local t2 = {} + for i,v in ipairs(t) do + t2[i] = v + end + return t2 +end + +local function table_remove(list, pos, n) + table.move(list, pos + n, #list + n, pos) +end + +local function table_replace_n_with(list, at, n, values) + local old = #list + debug("TRNW?", list, "AT", at, "N", n, "VALUES", values) + --assert(is_sequence(list)) + local nvalues = #values + local nils = n >= nvalues and (n - nvalues + 1) or 0 + if n ~= nvalues then + table.move(list, at + n, #list + nils, at + nvalues) + end + debug("....", list) + table.move(values, 1, nvalues, at, list) + --assert(is_sequence(list)) + debug("TRNW!", list) + assert(#list == old - n + #values) +end + +local stringify = typed("{string} -> string", function(tokens) + return '"'..table.concat(tokens, " "):gsub("\"", "\\")..'"' +end) + +local macro_expand + +local mark_noloop = typed("table, string, number -> ()", function(noloop, token, n) + noloop[token] = math.max(noloop[token] or 0, n) +end) + +local shift_noloop = typed("table, number -> ()", function(noloop, n) + for token, v in pairs(noloop) do + noloop[token] = v + n + end +end) + +local valid_noloop = typed("table, string, number -> boolean", function(noloop, token, n) + return noloop[token] == nil or noloop[token] < n +end) + +local replace_args = typed("Ctx, {string}, table, LineList, number -> ()", function(ctx, tokens, args, linelist, cur) + local i = 1 + local hash_next = false + local join_next = false + while true do + local token = tokens[i] + if not token then + break + end + if token == "#" then + hash_next = true + table.remove(tokens, i) + elseif token == "##" then + join_next = true + table.remove(tokens, i) + elseif args[token] then + macro_expand(ctx, args[token], linelist, cur, false) + if hash_next then + tokens[i] = stringify(args[token]) + hash_next = false + elseif join_next then + tokens[i - 1] = tokens[i - 1] .. table.concat(args[token], " ") + table.remove(tokens, i) + join_next = false + else + table_replace_n_with(tokens, i, 1, args[token]) + debug(token, args[token], tokens) + i = i + #args[token] + end + elseif join_next then + tokens[i - 1] = tokens[i - 1] .. tokens[i] + table.remove(tokens, i) + join_next = false + else + hash_next = false + join_next = false + i = i + 1 + end + end +end) + +macro_expand = typed("Ctx, {string}, LineList, number, boolean -> ()", function(ctx, tokens, linelist, cur, expr_mode) + local i = 1 + -- TODO propagate noloop into replace_args. recurse into macro_expand storing a proper offset internally. + local noloop = {} + while true do + ::continue:: + debug(i, tokens) + local token = tokens[i] + if not token then + break + end + if expr_mode then + if token == "defined" then + if tokens[i + 1] == "(" then + i = i + 2 + end + i = i + 2 + goto continue + end + end + local define = ctx.defines[token] + if define and valid_noloop(noloop, token, i) then + debug(token, define) + local repl = define.repl + if define.args then + if tokens[i + 1] == "(" then + local args, j = consume_parentheses(tokens, i + 1, linelist, cur) + debug("args:", #args, args) + local named_args = {} + for i = 1, #define.args do + named_args[define.args[i]] = args[i] or {} + end + local expansion = array_copy(repl) + replace_args(ctx, expansion, named_args, linelist, cur) + local nexpansion = #expansion + local n = j - i + 1 + if nexpansion == 0 then + table_remove(tokens, i, n) + else + table_replace_n_with(tokens, i, n, expansion) + end + shift_noloop(noloop, nexpansion - n) + mark_noloop(noloop, token, i + nexpansion - 1) + else + i = i + 1 + end + else + local ndefine = #define + if ndefine == 0 then + table.remove(tokens, i) + shift_noloop(noloop, -1) + elseif ndefine == 1 then + tokens[i] = define[1] + mark_noloop(noloop, token, i) + noloop[token] = math.max(noloop[token] or 0, i) + else + table_replace_n_with(tokens, i, 1, define) + mark_noloop(noloop, token, i + ndefine - 1) + end + end + else + i = i + 1 + end + end +end) + +local run_expression = typed("Ctx, {string} -> boolean", function(ctx, tks) + local exp = parse_expression(tks) + return eval_exp(ctx, exp) ~= 0 +end) + +cpp.parse_file = typed("string, FILE*?, Ctx? -> Ctx?, string?", function(filename, fd, ctx) + if not ctx then + ctx = { + incdirs = cpp_include_paths(), + defines = gcc_default_defines(), + ---@type any[] + ifmode = { true }, + output = {}, + current_dir = {} + } + typed.set_type(ctx, "Ctx") + -- if not absolute path + if not filename:match("^/") then + local found_name, found_fd = find_file(ctx, filename, "system") + if found_fd then + filename, fd = found_name, found_fd + end + end + end + + local current_dir = filename:gsub("/[^/]*$", "") + if current_dir == filename then + current_dir = "." + local found_name, found_fd = find_file(ctx, filename, "system") + if found_fd then + filename, fd = found_name, found_fd + end + end + table.insert(ctx.current_dir, current_dir) + + local err + if not fd then + fd, err = io.open(filename, "rb") + if not fd then + return nil, err + end + end + local linelist = cpp.initial_processing(fd) + + for _, lineitem in ipairs(linelist) do + lineitem.tk = cpp.tokenize(lineitem.line) + end + + local ifmode = ctx.ifmode + for cur, lineitem in ipairs(linelist) do + local line = lineitem.line + local tk = lineitem.tk + debug(filename, cur, ifmode[#ifmode], #ifmode, line) + + if #ifmode == 1 and (tk.directive == "elif" or tk.directive == "else" or tk.directive == "endif") then + return nil, "unexpected directive " .. tk.directive + end + + if tk.exp then + macro_expand(ctx, tk.exp, linelist, cur, true) + end + + if ifmode[#ifmode] == true then + if tk.directive then + debug(tk) + end + if tk.directive == "define" then + local k = tk.id + local v = tk.args and tk or tk.repl + ctx.defines[k] = v + elseif tk.directive == "undef" then + ctx.defines[tk.id] = nil + elseif tk.directive == "ifdef" then + table.insert(ifmode, (ctx.defines[tk.id] ~= nil)) + elseif tk.directive == "ifndef" then + table.insert(ifmode, (ctx.defines[tk.id] == nil)) + elseif tk.directive == "if" then + table.insert(ifmode, run_expression(ctx, tk.exp)) + elseif tk.directive == "elif" then + ifmode[#ifmode] = "skip" + elseif tk.directive == "else" then + ifmode[#ifmode] = not ifmode[#ifmode] + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + elseif tk.directive == "error" or tk.directive == "pragma" then + -- ignore + elseif tk.directive == "include" or tk.directive == "include_next" then + local name = tk.exp[1] + local mode = tk.exp.mode + local is_next = (tk.directive == "include_next") + local inc_filename, inc_fd, err = find_file(ctx, name, mode, is_next) + if not inc_filename then + -- fall back to trying to load an #include "..." as #include <...>; + -- this is necessary for Mac system headers + inc_filename, inc_fd, err = find_file(ctx, name, "system", is_next) + end + if not inc_filename then + return nil, name..":"..err + end + cpp.parse_file(inc_filename, inc_fd, ctx) + else + macro_expand(ctx, tk, linelist, cur, false) + table.insert(ctx.output, table.concat(tk, " ")) + end + elseif ifmode[#ifmode] == false then + if tk.directive == "ifdef" + or tk.directive == "ifndef" + or tk.directive == "if" then + table.insert(ifmode, "skip") + elseif tk.directive == "else" then + ifmode[#ifmode] = not ifmode[#ifmode] + elseif tk.directive == "elif" then + ifmode[#ifmode] = run_expression(ctx, tk.exp) + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + end + elseif ifmode[#ifmode] == "skip" then + if tk.directive == "ifdef" + or tk.directive == "ifndef" + or tk.directive == "if" then + table.insert(ifmode, "skip") + elseif tk.directive == "else" + or tk.directive == "elif" then + -- do nothing + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + end + end + end + + table.remove(ctx.current_dir) + + return ctx, nil +end) + +cpp.parse_context = typed("string, FILE*?, Ctx? -> Ctx?, string?", function(context, _, ctx) + if not ctx then + ctx = { + incdirs = {},--,cpp_include_paths(), + defines = {},--gcc_default_defines(), + ifmode = { true }, + output = {}, + current_dir = {} + } + typed.set_type(ctx, "Ctx") + end + + local fd = { + lines = function () + local n = 0 + return function () + if n == 0 then + n = 1 + return context + end + return nil + end + end, + close = function () + + end + } + + local linelist = cpp.initial_processing(fd) + + for _, lineitem in ipairs(linelist) do + lineitem.tk = cpp.tokenize(lineitem.line) + end + + local ifmode = ctx.ifmode + for cur, lineitem in ipairs(linelist) do + local line = lineitem.line + local tk = lineitem.tk + debug(cur, ifmode[#ifmode], #ifmode, line) + + if #ifmode == 1 and (tk.directive == "elif" or tk.directive == "else" or tk.directive == "endif") then + return nil, "unexpected directive " .. tk.directive + end + + if tk.exp then + macro_expand(ctx, tk.exp, linelist, cur, true) + end + + if ifmode[#ifmode] == true then + if tk.directive then + debug(tk) + end + if tk.directive == "define" then + local k = tk.id + local v = tk.args and tk or tk.repl + ctx.defines[k] = v + elseif tk.directive == "undef" then + ctx.defines[tk.id] = nil + elseif tk.directive == "ifdef" then + table.insert(ifmode, (ctx.defines[tk.id] ~= nil)) + elseif tk.directive == "ifndef" then + table.insert(ifmode, (ctx.defines[tk.id] == nil)) + elseif tk.directive == "if" then + table.insert(ifmode, run_expression(ctx, tk.exp)) + elseif tk.directive == "elif" then +---@diagnostic disable-next-line: assign-type-mismatch + ifmode[#ifmode] = "skip" + elseif tk.directive == "else" then + ifmode[#ifmode] = not ifmode[#ifmode] + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + elseif tk.directive == "error" or tk.directive == "pragma" then + -- ignore + elseif tk.directive == "include" or tk.directive == "include_next" then + local name = tk.exp[1] + local mode = tk.exp.mode + local is_next = (tk.directive == "include_next") + local inc_filename, inc_fd, err = find_file(ctx, name, mode, is_next) + if not inc_filename then + -- fall back to trying to load an #include "..." as #include <...>; + -- this is necessary for Mac system headers + inc_filename, inc_fd, err = find_file(ctx, name, "system", is_next) + end + if not inc_filename then + return nil, name..":"..err + end + cpp.parse_file(inc_filename, inc_fd, ctx) + else + macro_expand(ctx, tk, linelist, cur, false) + table.insert(ctx.output, table.concat(tk, " ")) + end + elseif ifmode[#ifmode] == false then + if tk.directive == "ifdef" + or tk.directive == "ifndef" + or tk.directive == "if" then + table.insert(ifmode, "skip") + elseif tk.directive == "else" then + ifmode[#ifmode] = not ifmode[#ifmode] + elseif tk.directive == "elif" then + ifmode[#ifmode] = run_expression(ctx, tk.exp) + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + end + elseif ifmode[#ifmode] == "skip" then + if tk.directive == "ifdef" + or tk.directive == "ifndef" + or tk.directive == "if" then + table.insert(ifmode, "skip") + elseif tk.directive == "else" + or tk.directive == "elif" then + -- do nothing + elseif tk.directive == "endif" then + table.remove(ifmode, #ifmode) + end + end + end + + table.remove(ctx.current_dir) + + return ctx, nil +end) + +cpp.expand_macro = typed("string, table -> string", function(macro, define_set) + local ctx = typed.table("Ctx", setmetatable({ + defines = define_set, + }, { __index = error, __newindex = error })) + local tokens = { macro } + local linelist = typed.table("LineList", { { nr = 1, line = macro } }) + macro_expand(ctx, tokens, linelist, 1, false) + return table.concat(tokens, " ") +end) + +return cpp diff --git a/script/plugins/ffi/c-parser/ctypes.lua b/script/plugins/ffi/c-parser/ctypes.lua new file mode 100644 index 000000000..115f78ab0 --- /dev/null +++ b/script/plugins/ffi/c-parser/ctypes.lua @@ -0,0 +1,604 @@ +local ctypes = { TESTMODE = false } + +local inspect = require("inspect") +local utility = require 'utility' +local util = require 'plugins.ffi.c-parser.util' +local typed = require("plugins.ffi.c-parser.typed") + +local equal_declarations + +local add_type = typed("TypeList, string, CType -> ()", function (lst, name, typ) + lst[name] = typ + table.insert(lst, { name = name, type = typ }) +end) + +-- Compare two lists of declarations +local equal_lists = typed("array, array -> boolean", function (l1, l2) + if #l1 ~= #l2 then + return false + end + for i, p1 in ipairs(l1) do + local p2 = l2[i] + if not equal_declarations(p1, p2) then + return false + end + end + return true +end) + +equal_declarations = function (t1, t2) + if type(t1) == "string" or type(t2) == "nil" then + return t1 == t2 + end + if not equal_declarations(t1.type, t2.type) then + return false + end + -- if not equal_lists(t1.name, t2.name) then + -- return false + -- end + if t1.type == "struct" then + if t1.name ~= t2.name then + return false + end + elseif t1.type == "function" then + if not equal_declarations(t1.ret.type, t2.ret.type) then + return false + end + if not equal_lists(t1.params, t2.params) then + return false + end + if t1.vararg ~= t2.vararg then + return false + end + end + return true +end + +local function is_modifier(str) + return str == "*" or str == "restrict" or str == "const" +end + +local function extract_modifiers(ret_pointer, items) + while is_modifier(items[1]) do + table.insert(ret_pointer, table.remove(items, 1)) + end +end + +local function get_name(name_src) + local ret_pointer = {} + if name_src == nil then + return false, "could not find a name: " .. inspect(name_src), nil + end + local name + local indices = {} + if type(name_src) == "string" then + if is_modifier(name_src) then + table.insert(ret_pointer, name_src) + else + name = name_src + end + else + name_src = name_src.declarator or name_src + if type(name_src[1]) == "table" then + extract_modifiers(ret_pointer, name_src[1]) + else + extract_modifiers(ret_pointer, name_src) + end + for _, part in ipairs(name_src) do + if part.idx then + table.insert(indices, part.idx) + end + end + name = name_src.name + end + return true, name, ret_pointer, next(indices) and indices +end + +local get_type +local get_fields + +local convert_value = typed("TypeList, table -> CType?, string?", function (lst, src) + local name = nil + local ret_pointer = {} + local idxs = nil + + if type(src.id) == "table" or type(src.ids) == "table" then + src.id = util.expandSingle(src.id) + src.ids = util.expandSingle(src.ids) + -- FIXME multiple ids, e.g.: int *x, y, *z; + local ok +---@diagnostic disable-next-line: cast-local-type + ok, name, ret_pointer, idxs = get_name(src.id or src.ids) + if not ok then + return nil, name + end + end + + local typ, err = get_type(lst, src, ret_pointer) + if not typ then + return nil, err + end + + return typed.table("CType", { + name = name, + type = typ, + idxs = idxs, + }), nil +end) + +local function convert_fields(lst, field_src, fields) + if field_src.ids then + for i, id in ipairs(field_src.ids) do + id.type = utility.deepCopy(field_src.type) + if id.type and id[1] then + for i, v in ipairs(id[1]) do + table.insert(id.type, v) + end + if id[1].idx then + id.isarray = true + end + id[1] = nil + end + table.insert(fields, id) + end + return true + end +end + +-- Interpret field data from `field_src` and add it to `fields`. +local function add_to_fields(lst, field_src, fields) + if type(field_src) == "table" and not field_src.ids then + assert(field_src.type.type == "union") + local subfields, err = get_fields(lst, field_src.type.fields) + if not subfields then + return nil, err + end + for _, subfield in ipairs(subfields) do + table.insert(fields, subfield) + end + return true + end + + if convert_fields(lst, field_src, fields) then + return true + end + local field, err = convert_value(lst, field_src) + if not field then + return nil, err + end +end + +get_fields = function (lst, fields_src) + local fields = {} + for _, field_src in ipairs(fields_src) do + local ok, err = add_to_fields(lst, field_src, fields) + if not ok then + return false, err + end + end + return fields +end + +local function get_enum_items(_, values) + local items = {} + for _, v in ipairs(values) do + -- TODO store enum actual values + table.insert(items, { name = v.id, value = v.value }) + end + return items +end + +local function getAnonymousID(t) + local v = tostring(t) + local _, e = v:find("table: 0x", 0, true) + return v:sub(e + 1) +end + +local get_composite_type = typed("TypeList, string?, string, array, string, function -> CType, string", + function (lst, specid, spectype, parts, partsfield, get_parts) + local name = specid + local key = spectype .. "@" .. (name or ctypes.TESTMODE and 'anonymous' or getAnonymousID(parts)) + + if not lst[key] then + -- Forward declaration + lst[key] = typed.table("CType", { + type = spectype, + name = name, + }) + end + + if parts then + local err + parts, err = get_parts(lst, parts) + if not parts then + return nil, err + end + end + + local typ = typed.table("CType", { + type = spectype, + name = name, + [partsfield] = parts, + }) + + if lst[key] then + if typ[partsfield] and lst[key][partsfield] and not equal_declarations(typ, lst[key]) then + return nil, "redeclaration for " .. key + end + end + add_type(lst, key, typ) + + return typ, key + end) + +local function get_structunion(lst, spec) + if spec.fields and not spec.fields[1] then + spec.fields = { spec.fields } + end + return get_composite_type(lst, spec.id, spec.type, spec.fields, "fields", get_fields) +end + +local function get_enum(lst, spec) + if spec.values and not spec.values[1] then + spec.values = { spec.values } + end + local typ, key = get_composite_type(lst, spec.id, spec.type, spec.values, "values", get_enum_items) + if typ.values then + for _, value in ipairs(typ.values) do + add_type(lst, value.name, typ) + end + end + return typ, key +end + +local function refer(lst, item, get_fn) + if item.id and not item.fields then + local key = item.type .. "@" .. item.id + local su_typ = lst[key] + if not su_typ then + return { + type = item.type, + name = { item.id }, + } + end + return su_typ + else + local typ, key = get_fn(lst, item) + if not typ then + return nil, key + end + return typ + end +end + +local calculate + +local function binop(val, fn) + local e1, e2 = calculate(val[1]), calculate(val[2]) + if type(e1) == "number" and type(e2) == "number" then + return fn(e1, e2) + else + return { e1, e2, op = val.op } + end +end + +calculate = function (val) + if type(val) == "string" then + return tonumber(val) + end + if val.op == "+" then + return binop(val, function (a, b) return a + b end) + elseif val.op == "-" then + return binop(val, function (a, b) return a - b end) + elseif val.op == "*" then + return binop(val, function (a, b) return a * b end) + elseif val.op == "/" then + return binop(val, function (a, b) return a / b end) + else + return val + end +end + +local base_types = { + ["char"] = true, + ["const"] = true, + ["bool"] = true, + ["double"] = true, + ["float"] = true, + ["int"] = true, + ["long"] = true, + ["short"] = true, + ["signed"] = true, + ["__signed"] = true, + ["__signed__"] = true, + ["unsigned"] = true, + ["void"] = true, + ["volatile"] = true, + ["ptrdiff_t"] = true, + ["size_t"] = true, + ["ssize_t"] = true, + ["wchar_t"] = true, + ["int8_t"] = true, + ["int16_t"] = true, + ["int32_t"] = true, + ["int64_t"] = true, + ["uint8_t"] = true, + ["uint16_t"] = true, + ["uint32_t"] = true, + ["uint64_t"] = true, + ["intptr_t"] = true, + ["uintptr_t"] = true, + ["__int8"] = true, + ["__int16"] = true, + ["__int32"] = true, + ["__int64"] = true, + ["_Bool"] = true, + ["__ptr32"] = true, + ["__ptr64"] = true, + ["_Complex"] = true, + ["complex"] = true, + ["__complex"] = true, + ["__complex__"] = true, + ["*"] = true, +} + +local qualifiers = { + ["extern"] = true, + ["static"] = true, + ["typedef"] = true, + ["restrict"] = true, + ["inline"] = true, + ["register"] = true, +} + +get_type = function (lst, spec, ret_pointer) + local tarr = {} + if type(spec.type) == "string" then + spec.type = { spec.type } + end + if spec.type and not spec.type[1] then + spec.type = { spec.type } + end + for _, part in ipairs(spec.type or spec) do + if qualifiers[part] then + -- skip + elseif base_types[part] then + table.insert(tarr, part) + elseif lst[part] and lst[part].type == "typedef" then + table.insert(tarr, part) + elseif type(part) == "table" and part.type == "struct" or part.type == "union" then + local su_typ, err = refer(lst, part, get_structunion) + if not su_typ then + return nil, err or "failed to refer struct" + end + table.insert(tarr, su_typ) + elseif type(part) == "table" and part.type == "enum" then + local en_typ, err = refer(lst, part, get_enum) + if not en_typ then + return nil, err or "failed to refer enum" + end + table.insert(tarr, en_typ) + else + return nil, "FIXME unknown type " .. inspect(spec) + end + end + if #ret_pointer > 0 then + for _, item in ipairs(ret_pointer) do + if type(item) == "table" and item.idx then + table.insert(tarr, { idx = calculate(item.idx) }) + else + table.insert(tarr, item) + end + end + end + return tarr, nil +end + +local function is_void(param) + return #param.type == 1 and param.type[1] == "void" +end + +local get_params = typed("TypeList, array -> array, boolean", function (lst, params_src) + local params = {} + local vararg = false + + assert(not params_src.param) + + for _, param_src in ipairs(params_src) do + if param_src == "..." then + vararg = true + else + local param, err = convert_value(lst, param_src.param) + if not param then + return nil, err + end + if not is_void(param) then + table.insert(params, param) + end + end + end + return params, vararg +end) + +local register_many = function (register_item_fn, lst, ids, spec) + for _, id in ipairs(ids) do + local ok, err = register_item_fn(lst, id, spec) + if not ok then + return false, err + end + end + return true, nil +end + +local register_decl_item = function (lst, id, spec) + local ok, name, ret_pointer, idxs = get_name(id.decl) + if not ok then + return false, name + end + assert(name) + local ret_type, err = get_type(lst, spec, ret_pointer) + if not ret_type then + return false, err + end + local typ + if id.decl.params then + local params, vararg = get_params(lst, id.decl.params) + if not params then + return false, vararg + end + typ = typed.table("CType", { + type = "function", + name = name, + idxs = idxs, + ret = { + type = ret_type, + }, + params = params, + vararg = vararg, + }) + else + typ = typed.table("CType", { + type = ret_type, + name = name, + idxs = idxs, + }) + end + + if lst[name] then + if not equal_declarations(lst[name], typ) then + return false, + "inconsistent declaration for " .. name .. " - " .. inspect(lst[name]) .. " VERSUS " .. inspect(typ) + end + end + add_type(lst, name, typ) + + return true, nil +end + +local register_decls = function (lst, ids, spec) + return register_many(register_decl_item, lst, ids, spec) +end + +-- Convert an table produced by an `extern inline` declaration +-- into one compatible with `register_decl`. +local function register_function(lst, item) + local id = { + decl = { + name = item.func.name, + params = item.func.params, + } + } + return register_decl_item(lst, id, item.spec) +end + +local function register_static_function(lst, item) + return true +end + +local register_typedef_item = typed("TypeList, table, table -> boolean, string?", function (lst, id, spec) + local ok, name, ret_pointer = get_name(id.decl) + if not ok then + return false, name or "failed" + end + local def, err = get_type(lst, spec, ret_pointer) + if not def then + return false, err or "failed" + end + local typ = typed.table("CType", { + type = "typedef", + name = name, + def = def, + }) + + if lst[name] then + if not equal_declarations(lst[name], typ) then + return false, + "inconsistent declaration for " .. name .. " - " .. inspect(lst[name]) .. " VERSUS " .. inspect(typ) + end + end + add_type(lst, name, typ) + + return true, nil +end) + +local register_typedefs = function (lst, item) + return register_many(register_typedef_item, lst, item.ids, item.spec) +end + +local function register_structunion(lst, item) + return get_structunion(lst, item.spec) +end + +local function register_enum(lst, item) + return get_enum(lst, item.spec) +end + +local function to_set(array) + local set = {} + for _, v in ipairs(array) do + set[v] = true + end + return set +end + +ctypes.register_types = typed("{Decl} -> TypeList?, string?", function (parsed) + local lst = typed.table("TypeList", {}) + for _, item in ipairs(parsed) do + typed.check(item.spec, "table") + local spec_set = to_set(item.spec) + if spec_set.extern and item.ids then + local ok, err = register_decls(lst, item.ids, item.spec) + if not ok then + return nil, err or "failed extern" + end + elseif spec_set.extern and item.func then + local ok, err = register_function(lst, item) + if not ok then + return nil, err or "failed extern" + end + elseif spec_set.static and item.func then + local ok, err = register_static_function(lst, item) + if not ok then + return nil, err or "failed static function" + end + elseif spec_set.typedef then + local ok, err = register_typedefs(lst, item) + if not ok then + return nil, err or "failed typedef" + end + else + local expandSingle = { + ["struct"] = true, + ["union"] = true, + ["enum"] = true, + } + local spec = util.expandSingle(item.spec) + if expandSingle[spec.type] then + item.spec = spec + end + if item.spec.type == "struct" or item.spec.type == "union" then + local ok, err = register_structunion(lst, item) + if not ok then + return nil, err or "failed struct/union" + end + elseif item.spec.type == "enum" then + local ok, err = register_enum(lst, item) + if not ok then + return nil, err or "failed enum" + end + elseif not item.ids then + -- forward declaration (e.g. "struct foo;") + elseif item.ids then + local ok, err = register_decls(lst, item.ids, item.spec) + if not ok then + return nil, err or "failed declaration" + end + else + return nil, "FIXME Uncategorized declaration: " .. inspect(item) + end + end + end + return lst, nil +end) + +return ctypes diff --git a/script/plugins/ffi/c-parser/typed.lua b/script/plugins/ffi/c-parser/typed.lua new file mode 100644 index 000000000..c84b87e3c --- /dev/null +++ b/script/plugins/ffi/c-parser/typed.lua @@ -0,0 +1,172 @@ +-------------------------------------------------------------------------------- +-- Lua programming with types +-------------------------------------------------------------------------------- + +local _, inspect = pcall(require, "inspect") +inspect = inspect or tostring + +local typed = {} + +local FAST = false + +local function is_sequence(xs) + if type(xs) ~= "table" then + return false + end + if FAST then + return true + end + local l = #xs + for k, _ in pairs(xs) do + if type(k) ~= "number" or k < 1 or k > l or math.floor(k) ~= k then + return false + end + end + return true +end + +local function type_of(t) + local mt = getmetatable(t) + return (mt and mt.__name) or (is_sequence(t) and "array") or type(t) +end + +local function set_type(t, typ) + local mt = getmetatable(t) + if not mt then + mt = {} + end + mt.__name = typ + return setmetatable(t, mt) +end + +local function typed_table(typ, t) + return set_type(t, typ) +end + +local function try_check(val, expected) + local optional = expected:match("^(.*)%?$") + if optional then + if val == nil then + return true + end + expected = optional + end + + local seq_type = expected:match("^{(.+)}$") + if seq_type then + if type(val) == "table" then + if FAST then + return true + end + local allok = true + for _, v in ipairs(val) do + local ok = try_check(v, seq_type) + if not ok then + allok = false + break + end + end + if allok then + return true + end + end + end + + -- if all we want is a table, don't perform further checks + if expected == "table" and type(val) == "table" then + return true + end + + local actual = type_of(val) + if actual == expected then + return true + end + return nil, actual +end + +local function typed_check(val, expected, category, n) + local ok, actual = try_check(val, expected) + if ok then + return true + end + if category and n then + error(("type error: %s %d: expected %s, got %s (%s)"):format(category, n, expected, actual, inspect(val)), category == "value" and 2 or 3) + else + error(("type error: expected %s, got %s (%s)"):format(expected, actual, inspect(val)), 2) + end +end + +local function split(s, sep) + local i, j, k = 1, s:find(sep, 1) + local out = {} + while j do + table.insert(out, s:sub(i, j - 1)) + i = k + 1 + j, k = s:find(sep, i) + end + table.insert(out, s:sub(i, #s)) + return out +end + +local function typed_function(types, fn) + local inp, outp = types:match("(.*[^%s])%s*%->%s*([^%s].*)") + local ins = split(inp, ",%s*") + local outs = split(outp, ",%s*") + return function(...) + local args = table.pack(...) + if args.n ~= #ins then + error("wrong number of inputs (given " .. args.n .. " - expects " .. types .. ")", 2) + end + for i = 1, #ins do + typed_check(args[i], ins[i], "argument", i) + end + local rets = table.pack(fn(...)) + if outp == "()" then + if rets.n ~= 0 then + error("wrong number of outputs (given " .. rets.n .. " - expects " .. types .. ")", 2) + end + else + if rets.n ~= #outs then + error("wrong number of outputs (given " .. rets.n .. " - expects " .. types .. ")", 2) + end + if outs[1] ~= "*" then + for i = 1, #outs do + typed_check(rets[i], outs[i], "return", i) + end + end + end + return table.unpack(rets, 1, rets.n) + end +end + +local typed_mt_on = { + __call = function(_, types, fn) + return typed_function(types, fn) + end +} + +local typed_mt_off = { + __call = function(_, _, fn) + return fn + end +} + +function typed.on() + typed.check = typed_check + typed.typed = typed_function + typed.set_type = set_type + typed.table = typed_table + setmetatable(typed, typed_mt_on) +end + +function typed.off() + typed.check = function() end + typed.typed = function(_, fn) return fn end + typed.set_type = function(t, _) return t end + typed.table = function(_, t) return t end + setmetatable(typed, typed_mt_off) +end + +typed.off() + +return typed diff --git a/script/plugins/ffi/c-parser/util.lua b/script/plugins/ffi/c-parser/util.lua new file mode 100644 index 000000000..cb493efaf --- /dev/null +++ b/script/plugins/ffi/c-parser/util.lua @@ -0,0 +1,28 @@ +local m = {} + +local function tableLenEqual(t, len) + for key, value in pairs(t) do + len = len - 1 + if len < 0 then + return false + end + end + return true +end + +local function isSingleNode(ast) + if type(ast) ~= 'table' then + return false + end + local len = #ast + return len == 1 and tableLenEqual(ast, len) +end + +function m.expandSingle(ast) + if isSingleNode(ast) then + return ast[1] + end + return ast +end + +return m diff --git a/script/plugins/ffi/cdefRerence.lua b/script/plugins/ffi/cdefRerence.lua new file mode 100644 index 000000000..54a8c2a79 --- /dev/null +++ b/script/plugins/ffi/cdefRerence.lua @@ -0,0 +1,37 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local reference = require 'core.reference' +local find = string.find +local remove = table.remove + +local function getCdefSourcePosition(ffi_state) + local cdef_position = ffi_state.ast.returns[1][1] + local source = vm.getFields(cdef_position) + for index, value in ipairs(source) do + local name = guide.getKeyName(value) + if name == 'cdef' then + return value.field.start + end + end +end + +---@async +return function () + local ffi_state + for uri in files.eachFile() do + if find(uri, "ffi.lua", 0, true) and find(uri, "meta", 0, true) then + ffi_state = files.getState(uri) + break + end + end + if ffi_state then + local res = reference(ffi_state.uri, getCdefSourcePosition(ffi_state), true) + if res then + if res[1].uri == ffi_state.uri then + remove(res, 1) + end + return res + end + end +end diff --git a/script/plugins/ffi/init.lua b/script/plugins/ffi/init.lua new file mode 100644 index 000000000..17159ff2e --- /dev/null +++ b/script/plugins/ffi/init.lua @@ -0,0 +1,374 @@ +local cdriver = require 'plugins.ffi.c-parser.cdriver' +local util = require 'plugins.ffi.c-parser.util' +local utility = require 'utility' +local SDBMHash = require 'SDBMHash' +local config = require 'config' +local fs = require 'bee.filesystem' +local ws = require 'workspace' +local furi = require 'file-uri' + +local namespace = 'ffi.namespace*.' + +--TODO:supprot 32bit ffi, need config +local knownTypes = { + ["bool"] = 'boolean', + ["char"] = 'integer', + ["short"] = 'integer', + ["int"] = 'integer', + ["long"] = 'integer', + ["float"] = 'number', + ["double"] = 'number', + ["signed"] = 'integer', + ["__signed"] = 'integer', + ["__signed__"] = 'integer', + ["unsigned"] = 'integer', + ["ptrdiff_t"] = 'integer', + ["size_t"] = 'integer', + ["ssize_t"] = 'integer', + ["wchar_t"] = 'integer', + ["int8_t"] = 'integer', + ["int16_t"] = 'integer', + ["int32_t"] = 'integer', + ["int64_t"] = 'integer', + ["uint8_t"] = 'integer', + ["uint16_t"] = 'integer', + ["uint32_t"] = 'integer', + ["uint64_t"] = 'integer', + ["intptr_t"] = 'integer', + ["uintptr_t"] = 'integer', + ["__int8"] = 'integer', + ["__int16"] = 'integer', + ["__int32"] = 'integer', + ["__int64"] = 'integer', + ["_Bool"] = 'boolean', + ["__ptr32"] = 'integer', + ["__ptr64"] = 'integer', + --[[ + ["_Complex"] = 1, + ["complex"] = 1, + ["__complex"] = 1, + ["__complex__"] = 1, +]] + ["unsignedchar"] = 'integer', + ["unsignedshort"] = 'integer', + ["unsignedint"] = 'integer', + ["unsignedlong"] = 'integer', + ["signedchar"] = 'integer', + ["signedshort"] = 'integer', + ["signedint"] = 'integer', + ["signedlong"] = 'integer', +} + +local blackKeyWord = { + ['and'] = "_and", + ['do'] = "_do", + ['elseif'] = "_elseif", + ['end'] = "_end", + ['false'] = "_false", + ['function'] = "_function", + ['in'] = "_in", + ['local'] = "_local", + ['nil'] = "_nil", + ['not'] = "_not", + ['or'] = "_or", + ['repeat'] = "_repeat", + ['then'] = "_then", + ['true'] = "_true", +} + +local invaildKeyWord = { + const = true, + restrict = true, + volatile = true, +} + +local constName = 'm' + +---@class ffi.builder +local builder = { switch_ast = utility.switch() } + +function builder:getTypeAst(name) + for i, asts in ipairs(self.globalAsts) do + if asts[name] then + return asts[name] + end + end +end + +function builder:needDeref(ast) + if not ast then + return false + end + if ast.type == 'typedef' then + -- maybe no name + ast = ast.def[1] + if type(ast) ~= 'table' then + return self:needDeref(self:getTypeAst(ast)) + end + end + if ast.type == 'struct' or ast.type == 'union' then + return true + else + return false + end +end + +function builder:getType(name) + if type(name) == 'table' then + local t = "" + local isStruct + if name.type then + t = t .. name.type .. "@" + name = name.name + end + for _, n in ipairs(name) do + if type(n) == 'table' then + n = n.full_name + end + if invaildKeyWord[n] then + goto continue + end + if not isStruct then + isStruct = self:needDeref(self:getTypeAst(n)) + end + t = t .. n + ::continue:: + end + -- deref ไธ€็บงๆŒ‡้’ˆ + if isStruct and t:sub(#t) == '*' then + t = t:sub(1, #t - 1) + end + name = t + end + if knownTypes[name] then + return knownTypes[name] + end + return namespace .. name +end + +function builder:isVoid(ast) + if not ast then + return false + end + if ast.type == 'typedef' then + return self:isVoid(self:getTypeAst(ast.def[1]) or ast.def[1]) + end + + local typename = type(ast.type) == 'table' and ast.type[1] or ast + if typename == 'void' then + return true + end + return self:isVoid(self:getTypeAst(typename)) +end + +local function getArrayType(arr) + if type(arr) ~= "table" then + return arr and '[]' or '' + end + local res = '' + for i, v in ipairs(arr) do + res = res .. '[]' + end + return res +end + +local function getValidName(name) + return blackKeyWord[name] or name +end + +function builder:buildStructOrUnion(lines, tt, name) + lines[#lines+1] = '---@class ' .. self:getType(name) + for _, field in ipairs(tt.fields or {}) do + if field.name and field.type then + lines[#lines+1] = ('---@field %s %s%s'):format(getValidName(field.name), self:getType(field.type), + getArrayType(field.isarray)) + end + end +end + +function builder:buildFunction(lines, tt, name) + local param_names = {} + for i, param in ipairs(tt.params or {}) do + local param_name = getValidName(param.name) + lines[#lines+1] = ('---@param %s %s%s'):format(param_name, self:getType(param.type), getArrayType(param.idxs)) + param_names[#param_names+1] = param_name + end + if tt.vararg then + param_names[#param_names+1] = '...' + end + if tt.ret then + if not self:isVoid(tt.ret) then + lines[#lines+1] = ('---@return %s'):format(self:getType(tt.ret.type)) + end + end + lines[#lines+1] = ('function m.%s(%s) end'):format(name, table.concat(param_names, ', ')) +end + +function builder:buildTypedef(lines, tt, name) + local def = tt.def[1] + if type(def) == 'table' and not def.name then + -- ่ฟ™ไธชๆ—ถๅ€™ๆฒกๆœ‰ไธป็ฑปๅž‹๏ผŒๅชๆœ‰ไธ€ไธชๅˆซๅ,็›ดๆŽฅๅˆ›ๅปบไธ€ไธชๅˆซๅ็ป“ๆž„ไฝ“ + self.switch_ast(def.type, self, lines, def, name) + else + lines[#lines+1] = ('---@alias %s %s'):format(self:getType(name), self:getType(def)) + end +end + +local calculate + +local function binop(enumer, val, fn) + local e1, e2 = calculate(enumer, val[1]), calculate(enumer, val[2]) + if type(e1) == "number" and type(e2) == "number" then + return fn(e1, e2) + else + return { e1, e2, op = val.op } + end +end +do + local ops = { + ['+'] = function (a, b) return a + b end, + ['-'] = function (a, b) return a - b end, + ['*'] = function (a, b) return a * b end, + ['/'] = function (a, b) return a / b end, + ['&'] = function (a, b) return a & b end, + ['|'] = function (a, b) return a | b end, + ['~'] = function (a, b) + if not b then + return ~a + end + return a ~ b + end, + ['<<'] = function (a, b) return a << b end, + ['>>'] = function (a, b) return a >> b end, + } + calculate = function (enumer, val) + if ops[val.op] then + return binop(enumer, val, ops[val.op]) + end + val = util.expandSingle(val) + if type(val) == "string" then + if enumer[val] then + return enumer[val] + end + return tonumber(val) + end + return val + end +end + +local function pushEnumValue(enumer, name, v) + v = tonumber(util.expandSingle(v)) + enumer[name] = v + enumer[#enumer+1] = v + return v +end + +function builder:buildEnum(lines, tt, name) + local enumer = {} + for i, val in ipairs(tt.values) do + local name = val.name + local v = val.value + if not v then + if i == 1 then + v = 0 + else + v = tt.values[i - 1].realValue + 1 + end + end + if type(v) == 'table' and v.op then + v = calculate(enumer, v) + end + if v then + val.realValue = pushEnumValue(enumer, name, v) + end + end + local alias = {} + for k, v in pairs(enumer) do + alias[#alias+1] = type(k) == 'number' and v or ([['%s']]):format(k) + if type(k) ~= 'number' then + lines[#lines+1] = ('m.%s = %s'):format(k, v) + end + end + if name then + lines[#lines+1] = ('---@alias %s %s'):format(self:getType(name), table.concat(alias, ' | ')) + end +end + +builder.switch_ast + :case 'struct' + :case 'union' + :call(builder.buildStructOrUnion) + :case 'enum' + :call(builder.buildEnum) + : case 'function' + :call(builder.buildFunction) + :case 'typedef' + :call(builder.buildTypedef) + +local function stringStartsWith(self, searchString, position) + if position == nil or position < 0 then + position = 0 + end + return string.sub(self, position + 1, #searchString + position) == searchString +end +local firstline = ('---@meta \n ---@class %s \n local %s = {}'):format(namespace, constName) +local m = {} +local function compileCode(lines, asts, b) + for _, ast in ipairs(asts) do + local tt = ast.type + + if tt.type == 'enum' and not stringStartsWith(ast.name, 'enum@') then + goto continue + end + if not tt.name then + if tt.type ~= 'enum' then + goto continue + end + --ๅŒฟๅๆžšไธพไนŸ่ฆๅˆ›ๅปบๅ…ทไฝ“็š„ๅ€ผ + lines = lines or { firstline } + builder.switch_ast(tt.type, b, lines, tt) + else + tt.full_name = ast.name + lines = lines or { firstline } + builder.switch_ast(tt.type, b, lines, tt, tt.full_name) + lines[#lines+1] = '\n' + end + ::continue:: + end + return lines +end +function m.compileCodes(codes) + ---@class ffi.builder + local b = setmetatable({ globalAsts = {}, cacheEnums = {} }, { __index = builder }) + + local lines + for _, code in ipairs(codes) do + local asts = cdriver.process_context(code) + if not asts then + goto continue + end + table.insert(b.globalAsts, asts) + lines = compileCode(lines, asts, b) + ::continue:: + end + return lines +end + +function m.build_single(codes, fileDir, uri) + local texts = m.compileCodes(codes) + if not texts then + return + end + local fullPath = fileDir /ws.getRelativePath(uri) + + if fullPath:stem():string():find '%.' then + local newPath = fullPath:parent_path() / (fullPath:stem():string():gsub('%.', '/') .. ".lua") + fs.create_directories(newPath:parent_path()) + fullPath = newPath + end + + utility.saveFile(tostring(fullPath), table.concat(texts, '\n')) + return true +end + +return m diff --git a/script/plugins/ffi/searchCode.lua b/script/plugins/ffi/searchCode.lua new file mode 100644 index 000000000..86dbc6803 --- /dev/null +++ b/script/plugins/ffi/searchCode.lua @@ -0,0 +1,69 @@ +local vm = require 'vm' + +local function getLiterals(arg) + local literals = vm.getLiterals(arg) + local res = {} + if not literals then + return res + end + for k, v in pairs(literals) do + if type(k) == 'string' then + res[#res+1] = k + end + end + return res +end + +---@return string[]? +local function getCode(CdefReference) + local target = CdefReference.target + if not (target.type == 'field' and target.parent.type == 'getfield') then + return + end + target = target.parent.parent + if target.type == 'call' then + return getLiterals(target.args and target.args[1]) + elseif target.type == 'local' then + local res = {} + for _, o in ipairs(target.ref) do + if o.parent.type ~= 'call' then + goto CONTINUE + end + local target = o.parent + local literals = vm.getLiterals(target.args and target.args[1]) + if not literals then + goto CONTINUE + end + for k, v in pairs(literals) do + if type(k) == 'string' then + res[#res+1] = k + end + end + ::CONTINUE:: + end + return res + end +end + +---@async +return function (CdefReference, target_uri) + if not CdefReference then + return nil + end + local codeResults + for i, v in ipairs(CdefReference) do + if v.uri ~= target_uri then + goto continue + end + local codes = getCode(v) + if not codes then + goto continue + end + for i, v in ipairs(codes) do + codeResults = codeResults or {} + codeResults[#codeResults+1] = v + end + ::continue:: + end + return codeResults +end diff --git a/script/plugins/init.lua b/script/plugins/init.lua new file mode 100644 index 000000000..28f902eae --- /dev/null +++ b/script/plugins/init.lua @@ -0,0 +1 @@ +require 'plugins.ffi' \ No newline at end of file diff --git a/script/plugins/nodeHelper.lua b/script/plugins/nodeHelper.lua new file mode 100644 index 000000000..3f90b1521 --- /dev/null +++ b/script/plugins/nodeHelper.lua @@ -0,0 +1,75 @@ +local vm = require 'vm' +local guide = require 'parser.guide' + +local _M = {} + +---@class node.match.pattern +---@field next node.match.pattern? + +local function deepCompare(source, pattern) + local type1, type2 = type(source), type(pattern) + if type1 ~= type2 then + return false + end + + if type1 ~= "table" then + return source == pattern + end + + for key2, value2 in pairs(pattern) do + local value1 = source[key2] + if value1 == nil or not deepCompare(value1, value2) then + return false + end + end + + return true +end + +---@param source parser.object +---@param pattern node.match.pattern +---@return boolean +function _M.matchPattern(source, pattern) + if source.type == 'local' then + if source.parent.type == 'funcargs' and source.parent.parent.type == 'function' then + for i, ref in ipairs(source.ref) do + if deepCompare(ref, pattern) then + return true + end + end + end + end + return false +end + +local vaildVarRegex = "()([a-zA-Z][a-zA-Z0-9_]*)()" +---ๅˆ›ๅปบ็ฑปๅž‹ *.field.fieldๅฝขๅผ็š„ pattern +---@param pattern string +---@return node.match.pattern?, string? +function _M.createFieldPattern(pattern) + local ret = { next = nil } + local next = ret + local init = 1 + while true do + local startpos, matched, endpos + if pattern:sub(1, 1) == "*" then + startpos, matched, endpos = init, "*", init + 1 + else + startpos, matched, endpos = vaildVarRegex:match(pattern, init) + end + if not startpos then + break + end + if startpos ~= init then + return nil, "invalid pattern" + end + local field = matched == "*" and { next = nil } + or { field = { type = 'field', matched }, type = 'getfield', next = nil } + next.next = field + next = field + pattern = pattern:sub(endpos) + end + return ret +end + +return _M diff --git a/script/progress.lua b/script/progress.lua index f1f371f5f..9aff4f89a 100644 --- a/script/progress.lua +++ b/script/progress.lua @@ -46,6 +46,10 @@ function mt:remove() end end +function mt:isRemoved() + return self._removed == true +end + ---่ฎพ็ฝฎๆ่ฟฐ ---@param message string # ๆ่ฟฐ function mt:setMessage(message) @@ -54,7 +58,7 @@ function mt:setMessage(message) end self._message = message self._dirty = true - self:_update() + self:update() end ---่ฎพ็ฝฎ็™พๅˆ†ๆฏ” @@ -65,16 +69,16 @@ function mt:setPercentage(per) end self._percentage = math.floor(per) self._dirty = true - self:_update() + self:update() end ---ๅ–ๆถˆไบ‹ไปถ function mt:onCancel(callback) self._onCancel = callback - self:_update() + self:update() end -function mt:_update() +function mt:update() if self._removed then return end @@ -136,28 +140,29 @@ end function m.update() ---@param prog progress for _, prog in pairs(m.map) do - if prog._removed then + if prog:isRemoved() then goto CONTINUE end - prog:_update() + prog:update() ::CONTINUE:: end end ---ๅˆ›ๅปบไธ€ไธช่ฟ›ๅบฆๆก ----@param uri uri +---@param uri? uri ---@param title string # ๆ ‡้ข˜ ---@param delay number # ่‡ณๅฐ‘็ป่ฟ‡่ฟ™ไนˆไน…ไน‹ๅŽๆ‰ไผšๆ˜พ็คบๅ‡บๆฅ function m.create(uri, title, delay) + local token = nextToken() local prog = setmetatable({ - _token = nextToken(), + _token = token, _title = title, _clock = time.time(), _delay = delay * 1000, _uri = uri, }, mt) - m.map[prog._token] = prog + m.map[token] = prog return prog end diff --git a/script/proto/converter.lua b/script/proto/converter.lua index 3f5ddebc2..e86e4904a 100644 --- a/script/proto/converter.lua +++ b/script/proto/converter.lua @@ -4,15 +4,28 @@ local encoder = require 'encoder' local offsetEncoding = 'utf16' +---@class converter local m = {} ---@alias position {line: integer, character: integer} -local function rawPackPosition(uri, pos) +---@param row integer +---@param col integer +---@return position +function m.position(row, col) + return { + line = row, + character = col, + } +end + +---@param state parser.state +---@param pos integer +---@return position +local function rawPackPosition(state, pos) local row, col = guide.rowColOf(pos) if col > 0 then - local state = files.getState(uri) - local text = files.getText(uri) + local text = state.lua if state and text then local lineOffset = state.lines[row] if lineOffset then @@ -32,17 +45,18 @@ local function rawPackPosition(uri, pos) } end -local function diffedPackPosition(uri, pos) - local state = files.getState(uri) +---@param state parser.state +---@param pos integer +---@return position +local function diffedPackPosition(state, pos) local offset = guide.positionToOffset(state, pos) - local originOffset = files.diffedOffsetBack(uri, offset) - local originLines = files.getOriginLines(uri) - local originPos = guide.offsetToPositionByLines(originLines, originOffset) + local originOffset = files.diffedOffsetBack(state, offset) + local originPos = guide.offsetToPositionByLines(state.originLines, originOffset) local row, col = guide.rowColOf(originPos) if col > 0 then - local text = files.getOriginText(uri) + local text = state.originText if text then - local lineOffset = originLines[row] + local lineOffset = state.originLines[row] local finalOffset = math.min(lineOffset + col - 1, #text + 1) col = encoder.len(offsetEncoding, text, lineOffset, finalOffset) end @@ -53,22 +67,24 @@ local function diffedPackPosition(uri, pos) } end ----@param uri uri +---@param state parser.state ---@param pos integer ---@return position -function m.packPosition(uri, pos) - if files.hasDiffed(uri) then - return diffedPackPosition(uri, pos) +function m.packPosition(state, pos) + if files.hasDiffed(state) then + return diffedPackPosition(state, pos) else - return rawPackPosition(uri, pos) + return rawPackPosition(state, pos) end end -local function rawUnpackPosition(uri, position) +---@param state parser.state +---@param position position +---@return integer +local function rawUnpackPosition(state, position) local row, col = position.line, position.character if col > 0 then - local state = files.getState(uri) - local text = files.getText(uri) + local text = state.lua if state and text then local lineOffset = state.lines[row] local textOffset = encoder.offset(offsetEncoding, text, col + 1, lineOffset) @@ -81,59 +97,69 @@ local function rawUnpackPosition(uri, position) return pos end -local function diffedUnpackPosition(uri, position) - local row, col = position.line, position.character - local originLines = files.getOriginLines(uri) +---@param state parser.state +---@param position position +---@return integer +local function diffedUnpackPosition(state, position) + local row, col = position.line, position.character if col > 0 then - local text = files.getOriginText(uri) - if text then - local lineOffset = originLines[row] - local textOffset = encoder.offset(offsetEncoding, text, col + 1, lineOffset) + local lineOffset = state.originLines[row] + if lineOffset then + local textOffset = encoder.offset(offsetEncoding, state.originText, col + 1, lineOffset) if textOffset and lineOffset then col = textOffset - lineOffset end end end - local state = files.getState(uri) local originPos = guide.positionOf(row, col) - local originOffset = guide.positionToOffsetByLines(originLines, originPos) - local offset = files.diffedOffset(uri, originOffset) + local originOffset = guide.positionToOffsetByLines(state.originLines, originPos) + local offset = files.diffedOffset(state, originOffset) local pos = guide.offsetToPosition(state, offset) return pos end ----@param uri uri +---@param state parser.state ---@param position position ---@return integer -function m.unpackPosition(uri, position) - if files.hasDiffed(uri) then - return diffedUnpackPosition(uri, position) +function m.unpackPosition(state, position) + if files.hasDiffed(state) then + return diffedUnpackPosition(state, position) else - return rawUnpackPosition(uri, position) + return rawUnpackPosition(state, position) end end ---@alias range {start: position, end: position} ----@param uri uri +---@param state parser.state ---@param start integer ---@param finish integer ---@return range -function m.packRange(uri, start, finish) +function m.packRange(state, start, finish) local range = { - start = m.packPosition(uri, start), - ['end'] = m.packPosition(uri, finish), + start = m.packPosition(state, start), + ['end'] = m.packPosition(state, finish), } return range end ----@param uri uri +---@param start position +---@param finish position +---@return range +function m.range(start, finish) + return { + start = start, + ['end'] = finish, + } +end + +---@param state parser.state ---@param range range ---@return integer start ---@return integer finish -function m.unpackRange(uri, range) - local start = m.unpackPosition(uri, range.start) - local finish = m.unpackPosition(uri, range['end']) +function m.unpackRange(state, range) + local start = m.unpackPosition(state, range.start) + local finish = m.unpackPosition(state, range['end']) return start, finish end @@ -181,4 +207,29 @@ function m.setOffsetEncoding(encoding) offsetEncoding = encoding:lower():gsub('%-', '') end +---@param s string +---@param i? integer +---@param j? integer +---@return integer +function m.len(s, i, j) + return encoder.len(offsetEncoding, s, i, j) +end + +---@class proto.command +---@field title string +---@field command string +---@field arguments any[] + +---@param title string +---@param command string +---@param arguments any[] +---@return proto.command +function m.command(title, command, arguments) + return { + title = title, + command = command, + arguments = arguments, + } +end + return m diff --git a/script/proto/define.lua b/script/proto/define.lua index ecdaf3065..77e7a77dd 100644 --- a/script/proto/define.lua +++ b/script/proto/define.lua @@ -136,6 +136,7 @@ m.TokenModifiers = { ["modification"] = 1 << 7, ["documentation"] = 1 << 8, ["defaultLibrary"] = 1 << 9, + ["global"] = 1 << 10, } m.TokenTypes = { @@ -165,23 +166,26 @@ m.TokenTypes = { } m.BuiltIn = { - ['basic'] = 'default', - ['bit'] = 'default', - ['bit32'] = 'default', - ['builtin'] = 'default', - ['coroutine'] = 'default', - ['debug'] = 'default', - ['ffi'] = 'default', - ['io'] = 'default', - ['jit'] = 'default', - ['math'] = 'default', - ['os'] = 'default', - ['package'] = 'default', - ['string'] = 'default', - ['table'] = 'default', - ['table.new'] = 'default', - ['table.clear'] = 'default', - ['utf8'] = 'default', + ['basic'] = 'default', + ['bit'] = 'default', + ['bit32'] = 'default', + ['builtin'] = 'default', + ['coroutine'] = 'default', + ['debug'] = 'default', + ['ffi'] = 'default', + ['io'] = 'default', + ['jit'] = 'default', + ['jit.profile'] = 'default', + ['jit.util'] = 'default', + ['math'] = 'default', + ['os'] = 'default', + ['package'] = 'default', + ['string'] = 'default', + ['table'] = 'default', + ['table.new'] = 'default', + ['table.clear'] = 'default', + ['utf8'] = 'default', + ['string.buffer'] = 'default', } m.InlayHintKind = { diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua index 9b0303cc5..61b8ff4bd 100644 --- a/script/proto/diagnostic.lua +++ b/script/proto/diagnostic.lua @@ -62,6 +62,7 @@ m.register { 'missing-return-value', 'redundant-return-value', 'missing-return', + 'missing-fields', } { group = 'unbalanced', severity = 'Warning', @@ -76,6 +77,7 @@ m.register { 'param-type-mismatch', 'cast-type-mismatch', 'return-type-mismatch', + 'inject-field', } { group = 'type-check', severity = 'Warning', @@ -100,6 +102,16 @@ m.register { status = 'Any', } +m.register { + 'incomplete-signature-doc', + 'missing-global-doc', + 'missing-local-export-doc', +} { + group = 'luadoc', + severity = 'Warning', + status = 'None', +} + m.register { 'codestyle-check' } { @@ -116,6 +128,14 @@ m.register { status = 'None', } +m.register { + 'name-style-check' +} { + group = 'codestyle', + severity = 'Warning', + status = 'None', +} + m.register { 'newline-call', 'newfield-call', @@ -171,19 +191,35 @@ m.register { status = 'Any', } +m.register { + 'global-element', +} { + group = 'conventions', + severity = 'Warning', + status = 'None' +} + m.register { 'duplicate-index', - 'duplicate-set-field', } { group = 'duplicate', severity = 'Warning', status = 'Any', } +m.register { + 'duplicate-set-field', +} { + group = 'duplicate', + severity = 'Warning', + status = 'Opened', +} + m.register { 'close-non-object', 'deprecated', 'discard-returns', + 'invisible', } { group = 'strict', severity = 'Warning', @@ -244,18 +280,20 @@ function m.getDiagAndErrNameMap() for name in pairs(m.getDefaultSeverity()) do names[name] = true end - local path = package.searchpath('parser.compile', package.path) - if path then - local f = io.open(path) - if f then - for line in f:lines() do - local name = line:match([=[type%s*=%s*['"](%u[%u_]+%u)['"]]=]) - if name then - local id = name:lower():gsub('_', '-') - names[id] = true + for _, fileName in ipairs {'parser.compile', 'parser.luadoc'} do + local path = package.searchpath(fileName, package.path) + if path then + local f = io.open(path) + if f then + for line in f:lines() do + local name = line:match([=[type%s*=%s*['"](%u[%u_]+%u)['"]]=]) + if name then + local id = name:lower():gsub('_', '-') + names[id] = true + end end + f:close() end - f:close() end end table.sort(names) diff --git a/script/proto/proto.lua b/script/proto/proto.lua index 024b17e50..b0d5d1a9c 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -1,4 +1,3 @@ -local subprocess = require 'bee.subprocess' local util = require 'utility' local await = require 'await' local pub = require 'pub' @@ -6,6 +5,10 @@ local jsonrpc = require 'jsonrpc' local define = require 'proto.define' local json = require 'json' local inspect = require 'inspect' +local platform = require 'bee.platform' +local fs = require 'bee.filesystem' +local net = require 'service.net' +local timer = require 'timer' local reqCounter = util.counter() @@ -23,11 +26,14 @@ local function logRecieve(proto) log.info('rpc recieve:', json.encode(proto)) end +---@class proto local m = {} m.ability = {} m.waiting = {} m.holdon = {} +m.mode = 'stdio' +m.client = nil function m.getMethodName(proto) if proto.method:sub(1, 2) == '$/' then @@ -45,7 +51,12 @@ end function m.send(data) local buf = jsonrpc.encode(data) logSend(buf) - io.write(buf) + if m.mode == 'stdio' then + io.write(buf) + elseif m.mode == 'socket' then + m.client:write(buf) + net.update() + end end function m.response(id, res) @@ -53,7 +64,10 @@ function m.response(id, res) log.error('Response id is nil!', inspect(res)) return end - assert(m.holdon[id]) + if not m.holdon[id] then + log.error('Unknown response id!', id) + return + end m.holdon[id] = nil local data = {} data.id = id @@ -66,7 +80,10 @@ function m.responseErr(id, code, message) log.error('Response id is nil!', inspect(message)) return end - assert(m.holdon[id]) + if not m.holdon[id] then + log.error('Unknown response id!', id) + return + end m.holdon[id] = nil m.send { id = id, @@ -181,7 +198,7 @@ function m.doMethod(proto) m.responseErr(proto.id, proto._closeReason or define.ErrorCodes.InternalError, proto._closeMessage or res) end end - ok, res = xpcall(abil, log.error, proto.params) + ok, res = xpcall(abil, log.error, proto.params, proto.id) await.delay() end) end @@ -212,12 +229,51 @@ function m.doResponse(proto) waiting.resume(proto.result) end -function m.listen() - subprocess.filemode(io.stdin, 'b') - subprocess.filemode(io.stdout, 'b') - io.stdin:setvbuf 'no' - io.stdout:setvbuf 'no' - pub.task('loadProto') +function m.listen(mode, socketPort) + m.mode = mode + if mode == 'stdio' then + if platform.os == 'windows' then + local windows = require 'bee.windows' + windows.filemode(io.stdin, 'b') + windows.filemode(io.stdout, 'b') + end + io.stdin:setvbuf 'no' + io.stdout:setvbuf 'no' + pub.task('loadProtoByStdio') + elseif mode == 'socket' then + local unixFolder = LOGPATH .. '/unix' + fs.create_directories(fs.path(unixFolder)) + local unixPath = unixFolder .. '/' .. tostring(socketPort) + + local server = net.listen('unix', unixPath) + + assert(server) + + local dummyClient = { + buf = '', + write = function (self, data) + self.buf = self.buf.. data + end, + update = function () end, + } + m.client = dummyClient + + local t = timer.loop(0.1, function () + net.update() + end) + + function server:on_accept(client) + t:remove() + m.client = client + client:write(dummyClient.buf) + net.update() + end + + pub.task('loadProtoBySocket', { + port = socketPort, + unixPath = unixPath, + }) + end end return m diff --git a/script/provider/capability.lua b/script/provider/capability.lua index 0f00cec18..4f7ec3885 100644 --- a/script/provider/capability.lua +++ b/script/provider/capability.lua @@ -7,48 +7,12 @@ local define = require 'proto.define' require 'provider.semantic-tokens' require 'provider.formatting' require 'provider.inlay-hint' +require 'provider.code-lens' local m = {} -local function testFileEvents(initer) - initer.fileOperations = { - didCreate = { - filters = { - { - pattern = { - glob = '**', - --matches = 'file', - options = platform.OS == 'Windows', - } - } - } - }, - didDelete = { - filters = { - { - pattern = { - glob = '**', - --matches = 'file', - options = platform.OS == 'Windows', - } - } - } - }, - didRename = { - filters = { - { - pattern = { - glob = '**', - --matches = 'file', - options = platform.OS == 'Windows', - } - } - } - }, - } -end - m.fillings = {} +m.resolvedMap = {} local function mergeFillings(provider) for _, filling in ipairs(m.fillings) do @@ -67,6 +31,22 @@ local function mergeFillings(provider) end end +local function resolve(t) + for k, v in pairs(t) do + if type(v) == 'table' then + resolve(v) + end + if type(v) == 'string' then + t[k] = v:gsub('%{(.-)%}', function (key) + return m.resolvedMap[key] or '' + end) + end + if type(v) == 'function' then + t[k] = v() + end + end +end + function m.getProvider() local provider = { offsetEncoding = client.getOffsetEncoding(), @@ -79,8 +59,6 @@ function m.getProvider() }, } - --testFileEvents() - nonil.enable() if not client.info.capabilities.textDocument.completion.dynamicRegistration or not client.info.capabilities.workspace.configuration then @@ -92,6 +70,7 @@ function m.getProvider() nonil.disable() mergeFillings(provider) + resolve(provider) return provider end @@ -100,4 +79,8 @@ function m.filling(t) m.fillings[#m.fillings+1] = t end +function m.resolve(key, value) + m.resolvedMap[key] = value +end + return m diff --git a/script/provider/code-lens.lua b/script/provider/code-lens.lua new file mode 100644 index 000000000..5802b17eb --- /dev/null +++ b/script/provider/code-lens.lua @@ -0,0 +1,29 @@ +local proto = require 'proto' +local client = require 'client' +local json = require 'json' +local config = require 'config' + +local function refresh() + if not client.isReady() then + return + end + if not client.getAbility 'workspace.codeLens.refreshSupport' then + return + end + log.debug('Refresh codeLens.') + proto.request('workspace/codeLens/refresh', json.null) +end + +config.watch(function (uri, key, value, oldValue) + if key == '' then + refresh() + end + if key:find '^Lua.runtime' + or key:find '^Lua.workspace' + or key:find '^Lua.codeLens' + or key:find '^files' then + refresh() + end +end) + +return {} diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index 46ea600f2..ebbe3e36a 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -23,6 +23,8 @@ local vm = require 'vm.vm' local m = {} m.cache = {} m.sleepRest = 0.0 +m.scopeDiagCount = 0 +m.pauseCount = 0 local function concat(t, sep) if type(t) ~= 'table' then @@ -32,8 +34,9 @@ local function concat(t, sep) end local function buildSyntaxError(uri, err) - local text = files.getText(uri) - if not text then + local state = files.getState(uri) + local text = files.getText(uri) + if not text or not state then return end local message = lang.script('PARSER_' .. err.type, err.info) @@ -58,16 +61,19 @@ local function buildSyntaxError(uri, err) rmessage = text:sub(rel.start, rel.finish) end local relUri = rel.uri or uri - relatedInformation[#relatedInformation+1] = { - message = rmessage, - location = converter.location(relUri, converter.packRange(relUri, rel.start, rel.finish)), - } + local relState = files.getState(relUri) + if relState then + relatedInformation[#relatedInformation+1] = { + message = rmessage, + location = converter.location(relUri, converter.packRange(relState, rel.start, rel.finish)), + } + end end end return { code = err.type:lower():gsub('_', '-'), - range = converter.packRange(uri, err.start, err.finish), + range = converter.packRange(state, err.start, err.finish), severity = define.DiagnosticSeverity[err.level], source = lang.script.DIAG_SYNTAX_CHECK, message = message, @@ -78,7 +84,8 @@ local function buildSyntaxError(uri, err) end local function buildDiagnostic(uri, diag) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return end @@ -90,16 +97,20 @@ local function buildDiagnostic(uri, diag) if not rtext then goto CONTINUE end + local relState = files.getState(rel.uri) + if not relState then + goto CONTINUE + end relatedInformation[#relatedInformation+1] = { message = rel.message or rtext:sub(rel.start, rel.finish), - location = converter.location(rel.uri, converter.packRange(rel.uri, rel.start, rel.finish)) + location = converter.location(rel.uri, converter.packRange(relState, rel.start, rel.finish)) } ::CONTINUE:: end end return { - range = converter.packRange(uri, diag.start, diag.finish), + range = converter.packRange(state, diag.start, diag.finish), source = lang.script.DIAG_DIAGNOSTICS, severity = diag.level, message = diag.message, @@ -172,14 +183,24 @@ function m.clearCacheExcept(uris) end end -function m.clearAll(force) +---@param uri? uri +---@param force? boolean +function m.clearAll(uri, force) + local scp + if uri then + scp = scope.getScope(uri) + end if force then for luri in files.eachFile() do - m.clear(luri, force) + if not scp or scope.getScope(luri) == scp then + m.clear(luri, force) + end end else for luri in pairs(m.cache) do - m.clear(luri) + if not scp or scope.getScope(luri) == scp then + m.clear(luri) + end end end end @@ -293,6 +314,11 @@ function m.doDiagnostic(uri, isScopeDiag) end m.cache[uri] = full + if not files.exists(uri) then + m.clear(uri) + return + end + proto.notify('textDocument/publishDiagnostics', { uri = uri, version = version, @@ -335,6 +361,11 @@ function m.resendDiagnostic(uri) return end + if not files.exists(uri) then + m.clear(uri) + return + end + local version = files.getVersion(uri) proto.notify('textDocument/publishDiagnostics', { @@ -382,22 +413,25 @@ function m.pullDiagnostic(uri, isScopeDiag) end ---@param uri uri -function m.refresh(uri) +function m.stopScopeDiag(uri) + local scp = scope.getScope(uri) + local scopeID = 'diagnosticsScope:' .. scp:getName() + await.close(scopeID) +end + +---@param event string +---@param uri uri +function m.refreshScopeDiag(event, uri) if not ws.isReady(uri) then return end - await.close('diag:' .. uri) - ---@async - await.call(function () - await.setID('diag:' .. uri) - await.sleep(0.1) - xpcall(m.doDiagnostic, log.error, uri) - end) + local eventConfig = config.get(uri, 'Lua.diagnostics.workspaceEvent') + + if eventConfig ~= event then + return + end - local scp = scope.getScope(uri) - local scopeID = 'diagnosticsScope:' .. scp:getName() - await.close(scopeID) ---@async await.call(function () local delay = config.get(uri, 'Lua.diagnostics.workspaceDelay') / 1000 @@ -409,6 +443,23 @@ function m.refresh(uri) end) end +---@param uri uri +function m.refresh(uri) + if not ws.isReady(uri) then + return + end + + await.close('diag:' .. uri) + ---@async + await.call(function () + await.setID('diag:' .. uri) + repeat + await.sleep(0.1) + until not m.isPaused() + xpcall(m.doDiagnostic, log.error, uri) + end) +end + ---@async local function askForDisable(uri) if m.dontAskedForDisable then @@ -457,12 +508,32 @@ local function askForDisable(uri) end end +local function clearMemory(finished) + if m.scopeDiagCount > 0 then + return + end + vm.clearNodeCache() + if finished then + collectgarbage() + collectgarbage() + end +end + ---@async function m.awaitDiagnosticsScope(suri, callback) local scp = scope.getScope(suri) + if scp.type == 'fallback' then + return + end while loading.count() > 0 do await.sleep(1.0) end + local finished + m.scopeDiagCount = m.scopeDiagCount + 1 + local scopeDiag = util.defer(function () + m.scopeDiagCount = m.scopeDiagCount - 1 + clearMemory(finished) + end) local clock = os.clock() local bar = progress.create(suri, lang.script.WORKSPACE_DIAGNOSTIC, 1) local cancelled @@ -501,6 +572,7 @@ function m.awaitDiagnosticsScope(suri, callback) end bar:remove() log.info(('Diagnostics scope [%s] finished, takes [%.3f] sec.'):format(scp:getName(), os.clock() - clock)) + finished = true end function m.diagnosticsScope(uri, force) @@ -508,7 +580,10 @@ function m.diagnosticsScope(uri, force) return end if not force and not config.get(uri, 'Lua.diagnostics.enable') then - m.clearAll() + m.clearAll(uri) + return + end + if not force and config.get(uri, 'Lua.diagnostics.workspaceDelay') < 0 then return end local scp = scope.getScope(uri) @@ -567,10 +642,29 @@ function m.pullDiagnosticScope(callback) end function m.refreshClient() + if not client.isReady() then + return + end + if not client.getAbility 'workspace.diagnostics.refreshSupport' then + return + end log.debug('Refresh client diagnostics') proto.request('workspace/diagnostic/refresh', json.null) end +---@return boolean +function m.isPaused() + return m.pauseCount > 0 +end + +function m.pause() + m.pauseCount = m.pauseCount + 1 +end + +function m.resume() + m.pauseCount = m.pauseCount - 1 +end + ws.watch(function (ev, uri) if ev == 'reload' then m.diagnosticsScope(uri) @@ -581,9 +675,17 @@ end) files.watch(function (ev, uri) ---@async if ev == 'remove' then m.clear(uri) + m.stopScopeDiag(uri) + m.refresh(uri) + m.refreshScopeDiag('OnSave', uri) + elseif ev == 'create' then + m.stopScopeDiag(uri) m.refresh(uri) + m.refreshScopeDiag('OnSave', uri) elseif ev == 'update' then + m.stopScopeDiag(uri) m.refresh(uri) + m.refreshScopeDiag('OnChange', uri) elseif ev == 'open' then if ws.isReady(uri) then m.resendDiagnostic(uri) @@ -594,12 +696,15 @@ files.watch(function (ev, uri) ---@async or ws.isIgnored(uri) then m.clear(uri) end + elseif ev == 'save' then + m.refreshScopeDiag('OnSave', uri) end end) config.watch(function (uri, key, value, oldValue) - if util.stringStartWith(key, 'Lua.diagnostics') - or util.stringStartWith(key, 'Lua.spell') then + if util.stringStartWith(key, 'Lua.diagnostics') + or util.stringStartWith(key, 'Lua.spell') + or util.stringStartWith(key, 'Lua.doc') then if value ~= oldValue then m.diagnosticsScope(uri) m.refreshClient() diff --git a/script/provider/formatting.lua b/script/provider/formatting.lua index 4ec5545a2..ea94db08c 100644 --- a/script/provider/formatting.lua +++ b/script/provider/formatting.lua @@ -82,16 +82,13 @@ function m.updateNonStandardSymbols(symbols) return end - local eqTokens = {} - for _, token in ipairs(symbols) do - if token:find("=") and token ~= "!=" then - table.insert(eqTokens, token) + for _, symbol in ipairs(symbols) do + if symbol == "//" then + codeFormat.set_clike_comments_symbol() end end - if #eqTokens ~= 0 then - codeFormat.set_nonstandard_symbol('=', eqTokens) - end + codeFormat.set_nonstandard_symbol() end config.watch(function(uri, key, value) diff --git a/script/provider/markdown.lua b/script/provider/markdown.lua index 507160734..fe1b26b29 100644 --- a/script/provider/markdown.lua +++ b/script/provider/markdown.lua @@ -71,6 +71,10 @@ function mt:string(nl) elseif obj.type == 'emptyline' then if #lines > 0 and lines[#lines] ~= '' then + if language ~= 'md' then + language = 'md' + lines[#lines+1] = '```' + end lines[#lines+1] = '' end elseif obj.type == 'markdown' then diff --git a/script/provider/name-style.lua b/script/provider/name-style.lua new file mode 100644 index 000000000..bdb20d80d --- /dev/null +++ b/script/provider/name-style.lua @@ -0,0 +1,28 @@ +local suc, codeFormat = pcall(require, 'code_format') +if not suc then + return +end + +local config = require 'config' + +local m = {} + +m.loaded = false + +function m.nameStyleCheck(uri, text) + if not m.loaded then + local value = config.get(nil, "Lua.nameStyle.config") + codeFormat.update_name_style_config(value) + m.loaded = true + end + + return codeFormat.name_style_analysis(uri, text) +end + +config.watch(function (uri, key, value) + if key == "Lua.nameStyle.config" then + codeFormat.update_name_style_config(value) + end +end) + +return m diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 58d32fb81..90c3a8ce0 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -6,7 +6,6 @@ local proto = require 'proto.proto' local define = require 'proto.define' local workspace = require 'workspace' local config = require 'config' -local library = require 'library' local client = require 'client' local pub = require 'pub' local lang = require 'language' @@ -19,21 +18,26 @@ local json = require 'json' local scope = require 'workspace.scope' local furi = require 'file-uri' local inspect = require 'inspect' -local markdown = require 'provider.markdown' local guide = require 'parser.guide' local fs = require 'bee.filesystem' -local jumpSource = require 'core.jump-source' + +require 'library' + +---@class provider +local m = {} + +m.attributes = {} local configLoaded = false ---@async -local function updateConfig(uri) +function m.updateConfig(uri) config.addNullSymbol(json.null) local specified = cfgLoader.loadLocalConfig(uri, CONFIGPATH) if specified then log.info('Load config from specified', CONFIGPATH) log.info(inspect(specified)) -- watch directory - filewatch.watch(workspace.getAbsolutePath(uri, CONFIGPATH):gsub('[^/\\]+$', '')) + filewatch.watch(workspace.getAbsolutePath(uri, CONFIGPATH):gsub('[^/\\]+$', ''), false) config.update(scope.override, specified) end @@ -61,11 +65,6 @@ local function updateConfig(uri) configLoaded = true end ----@class provider -local m = {} - -m.attributes = {} - function m.register(method) return function (attrs) m.attributes[method] = attrs @@ -84,7 +83,7 @@ filewatch.event(function (ev, path) ---@async for _, scp in ipairs(workspace.folders) do local configPath = workspace.getAbsolutePath(scp.uri, CONFIGPATH) if path == configPath then - updateConfig(scp.uri) + m.updateConfig(scp.uri) end end end @@ -92,7 +91,7 @@ filewatch.event(function (ev, path) ---@async for _, scp in ipairs(workspace.folders) do local rcPath = workspace.getAbsolutePath(scp.uri, '.luarc.json') if path == rcPath then - updateConfig(scp.uri) + m.updateConfig(scp.uri) end end end @@ -100,43 +99,45 @@ filewatch.event(function (ev, path) ---@async for _, scp in ipairs(workspace.folders) do local rcPath = workspace.getAbsolutePath(scp.uri, '.luarc.jsonc') if path == rcPath then - updateConfig(scp.uri) + m.updateConfig(scp.uri) end end end end) m.register 'initialize' { - function (params) + function(params) client.init(params) if params.rootUri then workspace.initRoot(params.rootUri) + cap.resolve('ROOT_PATH', furi.decode(params.rootUri):gsub('\\', '/') .. '/') end if params.workspaceFolders then for _, folder in ipairs(params.workspaceFolders) do - workspace.create(folder.uri) + workspace.create(files.getRealUri(folder.uri)) end elseif params.rootUri then - workspace.create(params.rootUri) + workspace.create(files.getRealUri(params.rootUri)) end - return { + local response = { capabilities = cap.getProvider(), serverInfo = { name = 'sumneko.lua', }, } + log.debug('Server init', inspect(response)) + return response end } m.register 'initialized'{ ---@async function (params) - files.init() local _ = progress.create(workspace.getFirstScope().uri, lang.script.WINDOW_INITIALIZING, 0.5) - updateConfig() + m.updateConfig() local registrations = {} if client.getAbility 'workspace.didChangeConfiguration.dynamicRegistration' then @@ -161,7 +162,7 @@ m.register 'initialized'{ m.register 'exit' { function () log.info('Server exited.') - os.exit(true) + os.exit(0, true) end } @@ -177,63 +178,63 @@ m.register 'workspace/didChangeConfiguration' { if CONFIGPATH then return end - updateConfig() - end -} - -m.register 'workspace/didCreateFiles' { - ---@async - function (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) - end - end - end -} - -m.register 'workspace/didDeleteFiles' { - function (params) - log.debug('workspace/didDeleteFiles', inspect(params)) - for _, file in ipairs(params.files) do - files.remove(file.uri) - local childs = files.getChildFiles(file.uri) - for _, uri in ipairs(childs) do - log.debug('workspace/didDeleteFiles#child', uri) - files.remove(uri) - end - end + m.updateConfig() end } m.register 'workspace/didRenameFiles' { + capability = { + workspace = { + fileOperations = { + didRename = { + filters = function () + local filters = {} + for i, scp in ipairs(workspace.folders) do + local path = furi.decode(scp.uri):gsub('\\', '/') + filters[i] = { + pattern = { + glob = path .. '/**', + options = { + ignoreCase = true, + } + }, + } + end + return filters + end + }, + }, + }, + }, ---@async function (params) log.debug('workspace/didRenameFiles', inspect(params)) + local renames = {} for _, file in ipairs(params.files) do - local text = files.getOriginText(file.oldUri) - if text then - files.remove(file.oldUri) - if workspace.isValidLuaUri(file.newUri) then - files.setText(file.newUri, text, false) - end + local oldUri = furi.normalize(file.oldUri) + local newUri = furi.normalize(file.newUri) + if workspace.isValidLuaUri(oldUri) + and workspace.isValidLuaUri(newUri) then + renames[#renames+1] = { + oldUri = oldUri, + newUri = newUri, + } end - local childs = files.getChildFiles(file.oldUri) + local childs = files.getChildFiles(oldUri) for _, uri in ipairs(childs) do - local ctext = files.getOriginText(uri) - if ctext then + if files.exists(uri) then local ouri = uri - local tail = ouri:sub(#file.oldUri) + local tail = ouri:sub(#oldUri) local nuri = file.newUri .. tail - log.debug('workspace/didRenameFiles#child', ouri, nuri) - files.remove(uri) - if workspace.isValidLuaUri(nuri) then - files.setText(nuri, text, false) - end + renames[#renames+1] = { + oldUri = ouri, + newUri = nuri, + } end end end + local core = require 'core.modifyRequirePath' + core(renames) end } @@ -250,12 +251,14 @@ m.register 'workspace/didChangeWorkspaceFolders' { function (params) log.debug('workspace/didChangeWorkspaceFolders', inspect(params)) for _, folder in ipairs(params.event.added) do - workspace.create(folder.uri) - updateConfig() - workspace.reload(scope.getScope(folder.uri)) + local uri = files.getRealUri(folder.uri) + workspace.create(uri) + m.updateConfig() + workspace.reload(scope.getScope(uri)) end for _, folder in ipairs(params.event.removed) do - workspace.remove(folder.uri) + local uri = files.getRealUri(folder.uri) + workspace.remove(uri) end end } @@ -264,12 +267,7 @@ m.register 'textDocument/didOpen' { ---@async function (params) local doc = params.textDocument - local scheme = furi.split(doc.uri) - local supports = config.get(doc.uri, 'Lua.workspace.supportScheme') - if not util.arrayHas(supports, scheme) then - return - end - local uri = files.getRealUri(doc.uri) + local uri = files.getRealUri(doc.uri) log.debug('didOpen', uri) -- there's a race condition between loading the config and this event getting called @@ -285,6 +283,8 @@ m.register 'textDocument/didOpen' { end) end files.open(uri) + workspace.awaitReady(uri) + files.compileState(uri) end } @@ -303,17 +303,13 @@ m.register 'textDocument/didClose' { m.register 'textDocument/didChange' { ---@async function (params) - local doc = params.textDocument - local scheme = furi.split(doc.uri) - local supports = config.get(doc.uri, 'Lua.workspace.supportScheme') - if not util.arrayHas(supports, scheme) then - return - end + local doc = params.textDocument local changes = params.contentChanges local uri = files.getRealUri(doc.uri) - local text = files.getOriginText(uri) + local text = files.getOriginText(uri) if not text then - files.setText(uri, pub.awaitTask('loadFile', furi.decode(uri)), false) + text = util.loadFile(furi.decode(uri)) + files.setText(uri, text, false) return end local rows = files.getCachedRows(uri) @@ -325,6 +321,22 @@ m.register 'textDocument/didChange' { end } +m.register 'textDocument/didSave' { + capability = { + textDocumentSync = { + save = { + includeText = false, + }, + } + }, + ---@async + function (params) + local doc = params.textDocument + local uri = files.getRealUri(doc.uri) + files.onWatch('save', uri) + end +} + m.register 'textDocument/hover' { capability = { hoverProvider = true, @@ -348,10 +360,11 @@ m.register 'textDocument/hover' { end local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_HOVER, 0.5) local core = require 'core.hover' - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local hover, source = core.byUri(uri, pos) if not hover or not source then return nil @@ -361,7 +374,7 @@ m.register 'textDocument/hover' { value = tostring(hover), kind = 'markdown', }, - range = converter.packRange(uri, source.start, source.finish), + range = converter.packRange(state, source.start, source.finish), } end } @@ -375,29 +388,42 @@ m.register 'textDocument/definition' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then + local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) + local state = files.getState(uri) + if not state then return nil end - local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) local core = require 'core.definition' - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core(uri, pos) if not result then return nil end local response = {} for i, info in ipairs(result) do + ---@type uri local targetUri = info.uri if targetUri then - if client.getAbility 'textDocument.definition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(uri, info.source.start, info.source.finish) - ) + local targetState = files.getState(targetUri) + if targetState then + if client.getAbility 'textDocument.definition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(state, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + ) + end else - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) + response[i] = converter.location( + targetUri, + converter.range( + converter.position(guide.rowColOf(info.target.start)), + converter.position(guide.rowColOf(info.target.finish)) + ) ) end end @@ -415,30 +441,35 @@ m.register 'textDocument/typeDefinition' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then - return nil - end local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local state = files.getState(uri) + if not state then + return + end local core = require 'core.type-definition' - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core(uri, pos) if not result then return nil end local response = {} for i, info in ipairs(result) do + ---@type uri local targetUri = info.uri if targetUri then - if client.getAbility 'textDocument.typeDefinition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(uri, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - ) + local targetState = files.getState(targetUri) + if targetState then + if client.getAbility 'textDocument.typeDefinition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(state, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + ) + end end end end @@ -455,22 +486,27 @@ m.register 'textDocument/references' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then + local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) + local state = files.getState(uri) + if not state then return nil end - local _ = 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) + local pos = converter.unpackPosition(state, params.position) + local result = core(uri, pos, params.context.includeDeclaration) if not result then return nil end local response = {} for i, info in ipairs(result) do + ---@type uri local targetUri = info.uri - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - ) + local targetState = files.getState(targetUri) + if targetState then + response[#response+1] = converter.location(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + ) + end end return response end @@ -485,10 +521,11 @@ m.register 'textDocument/documentHighlight' { local core = require 'core.highlight' local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core(uri, pos) if not result then return nil @@ -496,7 +533,7 @@ m.register 'textDocument/documentHighlight' { local response = {} for _, info in ipairs(result) do response[#response+1] = { - range = converter.packRange(uri, info.start, info.finish), + range = converter.packRange(state, info.start, info.finish), kind = info.kind, } end @@ -515,12 +552,13 @@ m.register 'textDocument/rename' { function (params) local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then + local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_RENAME, 0.5) + local state = files.getState(uri) + if not state then return nil end - local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_RENAME, 0.5) local core = require 'core.rename' - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core.rename(uri, pos, params.newName) if not result then return nil @@ -529,12 +567,16 @@ m.register 'textDocument/rename' { changes = {}, } for _, info in ipairs(result) do - local ruri = info.uri - if not workspaceEdit.changes[ruri] then - workspaceEdit.changes[ruri] = {} + ---@type uri + local ruri = info.uri + local rstate = files.getState(ruri) + if rstate then + if not workspaceEdit.changes[ruri] then + workspaceEdit.changes[ruri] = {} + end + local textEdit = converter.textEdit(converter.packRange(rstate, info.start, info.finish), info.text) + workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit end - local textEdit = converter.textEdit(converter.packRange(ruri, info.start, info.finish), info.text) - workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit end return workspaceEdit end @@ -545,16 +587,17 @@ m.register 'textDocument/prepareRename' { function (params) local core = require 'core.rename' local uri = files.getRealUri(params.textDocument.uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core.prepareRename(uri, pos) if not result then return nil end return { - range = converter.packRange(uri, result.start, result.finish), + range = converter.packRange(state, result.start, result.finish), placeholder = result.text, } end @@ -565,27 +608,16 @@ m.register 'textDocument/completion' { function (params) local uri = files.getRealUri(params.textDocument.uri) if not workspace.isReady(uri) then - local count, max = workspace.getLoadingProcess(uri) - return { - { - label = lang.script('HOVER_WS_LOADING', count, max), - textEdit = { - range = { - start = params.position, - ['end'] = params.position, - }, - newText = '', - }, - } - } + return nil end local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_COMPLETION, 0.5) --log.info(util.dump(params)) local core = require 'core.completion' --log.debug('textDocument/completion') --log.debug('completion:', params.context and params.context.triggerKind, params.context and params.context.triggerCharacter) - if not files.exists(uri) then - return nil + local state = files.getState(uri) + if not state then + return end local triggerCharacter = params.context and params.context.triggerCharacter if config.get(uri, 'editor.acceptSuggestionOnEnter') ~= 'off' then @@ -597,7 +629,7 @@ m.register 'textDocument/completion' { end --await.setPriority(1000) local clock = os.clock() - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local result = core.completion(uri, pos, triggerCharacter) local passed = os.clock() - clock if passed > 0.1 then @@ -624,7 +656,7 @@ m.register 'textDocument/completion' { command = res.command, textEdit = res.textEdit and { range = converter.packRange( - uri, + state, res.textEdit.start, res.textEdit.finish ), @@ -635,7 +667,7 @@ m.register 'textDocument/completion' { for j, edit in ipairs(res.additionalTextEdits) do t[j] = { range = converter.packRange( - uri, + state, edit.start, edit.finish ), @@ -689,9 +721,13 @@ m.register 'completionItem/resolve' { local id = item.data.id local uri = item.data.uri --await.setPriority(1000) + local state = files.getState(uri) + if not state then + return item + end local resolved = core.resolve(id) if not resolved then - return nil + return item end item.detail = resolved.detail or item.detail item.documentation = resolved.description and { @@ -703,7 +739,7 @@ m.register 'completionItem/resolve' { for j, edit in ipairs(resolved.additionalTextEdits) do t[j] = { range = converter.packRange( - uri, + state, edit.start, edit.finish ), @@ -730,11 +766,12 @@ m.register 'textDocument/signatureHelp' { return nil end workspace.awaitReady(uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) - local pos = converter.unpackPosition(uri, params.position) + local pos = converter.unpackPosition(state, params.position) local core = require 'core.signature' local results = core(uri, pos) if not results then @@ -746,15 +783,15 @@ m.register 'textDocument/signatureHelp' { for j, param in ipairs(result.params) do parameters[j] = { label = { - param.label[1], - param.label[2], + converter.len(result.label, 1, param.label[1]), + converter.len(result.label, 1, param.label[2]), } } end infos[i] = { label = result.label, parameters = parameters, - activeParameter = result.index - 1, + activeParameter = math.max(0, result.index - 1), documentation = result.description and { value = tostring(result.description), kind = 'markdown', @@ -777,7 +814,10 @@ m.register 'textDocument/documentSymbol' { local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) - + local state = files.getState(uri) + if not state then + return nil + end local core = require 'core.document-symbol' local symbols = core(uri) if not symbols then @@ -788,17 +828,17 @@ m.register 'textDocument/documentSymbol' { local function convert(symbol) await.delay() symbol.range = converter.packRange( - uri, + state, symbol.range[1], symbol.range[2] ) symbol.selectionRange = converter.packRange( - uri, + state, symbol.selectionRange[1], symbol.selectionRange[2] ) if symbol.name == '' then - symbol.name = lang.script.SYMBOL_ANONYMOUS + symbol.name = ' ' end symbol.valueRange = nil if symbol.children then @@ -829,16 +869,20 @@ m.register 'textDocument/codeAction' { }, }, abortByFileUpdate = true, + ---@async function (params) local core = require 'core.code-action' local uri = files.getRealUri(params.textDocument.uri) local range = params.range local diagnostics = params.context.diagnostics - if not files.exists(uri) then + workspace.awaitReady(uri) + + local state = files.getState(uri) + if not state then return nil end - local start, finish = converter.unpackRange(uri, range) + local start, finish = converter.unpackRange(state, range) local results = core(uri, start, finish, diagnostics) if not results or #results == 0 then @@ -847,11 +891,15 @@ m.register 'textDocument/codeAction' { for _, res in ipairs(results) do if res.edit then + ---@param turi uri for turi, changes in pairs(res.edit.changes) do - for _, change in ipairs(changes) do - change.range = converter.packRange(turi, change.start, change.finish) - change.start = nil - change.finish = nil + local tstate = files.getState(turi) + if tstate then + for _, change in ipairs(changes) do + change.range = converter.packRange(tstate, change.start, change.finish) + change.start = nil + change.finish = nil + end end end end @@ -861,6 +909,53 @@ m.register 'textDocument/codeAction' { end } +m.register 'textDocument/codeLens' { + capability = { + codeLensProvider = { + resolveProvider = true, + } + }, + --abortByFileUpdate = true, + ---@async + function (params) + local uri = files.getRealUri(params.textDocument.uri) + if not config.get(uri, 'Lua.codeLens.enable') then + return + end + workspace.awaitReady(uri) + local state = files.getState(uri) + if not state then + return nil + end + local core = require 'core.code-lens' + local results = core.codeLens(uri) + if not results then + return nil + end + local codeLens = {} + for _, result in ipairs(results) do + codeLens[#codeLens+1] = { + range = converter.packRange(state, result.position, result.position), + data = { + uri = uri, + id = result.id, + }, + } + end + return codeLens + end +} + +m.register 'codeLens/resolve' { + ---@async + function (codeLen) + local core = require 'core.code-lens' + local command = core.resolve(codeLen.data.uri, codeLen.data.id) + codeLen.command = command or converter.command('...', '', {}) + return codeLen + end +} + m.register 'workspace/executeCommand' { capability = { executeCommandProvider = { @@ -869,6 +964,7 @@ m.register 'workspace/executeCommand' { 'lua.solve', 'lua.jsonToLua', 'lua.setConfig', + 'lua.getConfig', 'lua.autoRequire', }, }, @@ -887,10 +983,21 @@ m.register 'workspace/executeCommand' { return core(params.arguments[1]) elseif command == 'lua.setConfig' then local core = require 'core.command.setConfig' - return core(params.arguments[1]) + return core(params.arguments) + elseif command == 'lua.getConfig' then + local core = require 'core.command.getConfig' + return core(params.arguments) elseif command == 'lua.autoRequire' then local core = require 'core.command.autoRequire' return core(params.arguments[1]) + elseif command == 'lua.exportDocument' then + local core = require 'core.command.exportDocument' + core(params.arguments) + elseif command == 'lua.reloadFFIMeta' then + local core = require 'core.command.reloadFFIMeta' + for _, scp in ipairs(workspace.folders) do + core(scp.uri) + end end end } @@ -905,28 +1012,38 @@ m.register 'workspace/symbol' { local _ = progress.create(workspace.getFirstScope().uri, lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) local core = require 'core.workspace-symbol' - local symbols = core(params.query) + local symbols = core(params.query, nil, true) if not symbols or #symbols == 0 then return nil end local function convert(symbol) - symbol.location = converter.location( - symbol.uri, - converter.packRange( - symbol.uri, - symbol.range[1], - symbol.range[2] + local uri = guide.getUri(symbol.source) + local state = files.getState(uri) + if not state then + return nil + end + return { + name = symbol.name, + kind = symbol.skind, + location = converter.location( + uri, + converter.packRange( + state, + symbol.source.start, + symbol.source.finish + ) ) - ) - symbol.uri = nil + } end + local results = {} + for _, symbol in ipairs(symbols) do - convert(symbol) + results[#results+1] = convert(symbol) end - return symbols + return results end } @@ -954,11 +1071,13 @@ client.event(function (ev) full = true, }, }, + abortByFileUpdate = true, ---@async function (params) log.debug('textDocument/semanticTokens/full') local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) + await.sleep(0.0) local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) local core = require 'core.semantic-tokens' local results = core(uri, 0, math.huge) @@ -981,14 +1100,20 @@ m.register 'textDocument/semanticTokens/range' { range = true, }, }, + abortByFileUpdate = true, ---@async function (params) log.debug('textDocument/semanticTokens/range') local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) + await.sleep(0.0) + local state = files.getState(uri) + if not state then + return nil + end local core = require 'core.semantic-tokens' - local start, finish = converter.unpackRange(uri, params.range) + local start, finish = converter.unpackRange(state, params.range) local results = core(uri, start, finish) return { data = results @@ -1008,6 +1133,10 @@ m.register 'textDocument/foldingRange' { if not files.exists(uri) then return nil end + local state = files.getState(uri) + if not state then + return nil + end local regions = core(uri) if not regions then return nil @@ -1015,8 +1144,8 @@ m.register 'textDocument/foldingRange' { local results = {} for _, region in ipairs(regions) do - local startLine = converter.packPosition(uri, region.start).line - local endLine = converter.packPosition(uri, region.finish).line + local startLine = converter.packPosition(state, region.start).line + local endLine = converter.packPosition(state, region.finish).line if not region.hideLastLine then endLine = endLine - 1 end @@ -1042,7 +1171,8 @@ m.register 'textDocument/documentColor' { local color = require 'core.color' local uri = files.getRealUri(params.textDocument.uri) workspace.awaitReady(uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end local colors = color.colors(uri) @@ -1052,7 +1182,7 @@ m.register 'textDocument/documentColor' { local results = {} for _, colorValue in ipairs(colors) do results[#results+1] = { - range = converter.packRange(uri, colorValue.start, colorValue.finish), + range = converter.packRange(state, colorValue.start, colorValue.finish), color = colorValue.color } end @@ -1080,7 +1210,9 @@ m.register '$/status/click' { local titleDiagnostic = lang.script.WINDOW_LUA_STATUS_DIAGNOSIS_TITLE local result = client.awaitRequestMessage('Info', lang.script.WINDOW_LUA_STATUS_DIAGNOSIS_MSG, { titleDiagnostic, - DEVELOP and 'Restart Server', + DEVELOP and 'Restart Server' or nil, + DEVELOP and 'Clear Node Cache' or nil, + DEVELOP and 'GC' or nil, }) if not result then return @@ -1092,8 +1224,16 @@ m.register '$/status/click' { end elseif result == 'Restart Server' then local diag = require 'provider.diagnostic' - diag.clearAll(true) + diag.clearAll(nil, true) os.exit(0, true) + elseif result == 'Clear Node Cache' then + local vm = require 'vm' + vm.clearNodeCache() + collectgarbage() + collectgarbage() + elseif result == 'GC' then + collectgarbage() + collectgarbage() end end } @@ -1106,7 +1246,8 @@ m.register 'textDocument/formatting' { function(params) local uri = files.getRealUri(params.textDocument.uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end @@ -1126,7 +1267,7 @@ m.register 'textDocument/formatting' { local results = {} for i, edit in ipairs(edits) do results[i] = { - range = converter.packRange(uri, edit.start, edit.finish), + range = converter.packRange(state, edit.start, edit.finish), newText = edit.text, } end @@ -1143,7 +1284,8 @@ m.register 'textDocument/rangeFormatting' { function(params) local uri = files.getRealUri(params.textDocument.uri) - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end @@ -1163,7 +1305,7 @@ m.register 'textDocument/rangeFormatting' { local results = {} for i, edit in ipairs(edits) do results[i] = { - range = converter.packRange(uri, edit.start, edit.finish), + range = converter.packRange(state, edit.start, edit.finish), newText = edit.text, } end @@ -1186,12 +1328,13 @@ m.register 'textDocument/onTypeFormatting' { workspace.awaitReady(uri) local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) local ch = params.ch - if not files.exists(uri) then + local state = files.getState(uri) + if not state then return nil end local core = require 'core.type-formatting' - local pos = converter.unpackPosition(uri, params.position) - local edits = core(uri, pos, ch) + local pos = converter.unpackPosition(state, params.position) + local edits = core(uri, pos, ch, params.options) if not edits or #edits == 0 then return nil end @@ -1202,7 +1345,7 @@ m.register 'textDocument/onTypeFormatting' { local results = {} for i, edit in ipairs(edits) do results[i] = { - range = converter.packRange(uri, edit.start, edit.finish), + range = converter.packRange(state, edit.start, edit.finish), newText = edit.text:gsub('\t', tab), } end @@ -1224,14 +1367,18 @@ m.register '$/requestHint' { return end workspace.awaitReady(uri) + local state = files.getState(uri) + if not state then + return + end local core = require 'core.hint' - local start, finish = converter.unpackRange(uri, params.range) + local start, finish = converter.unpackRange(state, params.range) local results = core(uri, start, finish) local hintResults = {} for i, res in ipairs(results) do hintResults[i] = { text = res.text, - pos = converter.packPosition(uri, res.offset), + pos = converter.packPosition(state, res.offset), kind = res.kind, } end @@ -1249,33 +1396,39 @@ m.register 'textDocument/inlayHint' { function (params) local uri = files.getRealUri(params.textDocument.uri) if not config.get(uri, 'Lua.hint.enable') then - return + return nil end workspace.awaitReady(uri) local core = require 'core.hint' - local start, finish = converter.unpackRange(uri, params.range) + local state = files.getState(uri) + if not state then + return nil + end + local start, finish = converter.unpackRange(state, params.range) local results = core(uri, start, finish) local hintResults = {} for i, res in ipairs(results) do + local luri = res.source and guide.getUri(res.source) + local lstate = files.getState(luri) hintResults[i] = { label = { { value = res.text, tooltip = res.tooltip, - location = res.source and converter.location( - guide.getUri(res.source), - converter.packRange( - guide.getUri(res.source), - res.source.start, - res.source.finish - ) - ), + location = lstate and converter.location( + luri, + converter.packRange( + lstate, + res.source.start, + res.source.finish + ) + ), }, }, - position = converter.packPosition(uri, res.offset), + position = converter.packPosition(state, res.offset), kind = res.kind, - paddingLeft = true, - paddingRight = true, + paddingLeft = res.kind == 1, + paddingRight = res.kind == 2, } end return hintResults @@ -1411,6 +1564,37 @@ m.register '$/api/report' { end } +m.register '$/psi/view' { + ---@async + function (params) + local uri = files.getRealUri(params.uri) + workspace.awaitReady(uri) + local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) + if not files.exists(uri) then + return nil + end + local core = require 'core.view.psi-view' + local result = core(uri) + return result + end +} + +m.register '$/psi/select' { + ---@async + function(params) + local uri = files.getRealUri(params.uri) + workspace.awaitReady(uri) + local _ = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) + if not files.exists(uri) then + return nil + end + local core = require 'core.view.psi-select' + local result = core(uri, params.position) + return result + end +} + + local function refreshStatusBar() local valid = config.get(nil, 'Lua.window.statusBar') for _, scp in ipairs(workspace.folders) do @@ -1449,3 +1633,5 @@ files.watch(function (ev, uri) end end end) + +return m diff --git a/script/pub/pub.lua b/script/pub/pub.lua index 1e9b6c8f6..5bbb381c6 100644 --- a/script/pub/pub.lua +++ b/script/pub/pub.lua @@ -24,7 +24,7 @@ log = require 'brave.log' xpcall(dofile, log.error, %q) local brave = require 'brave' -brave.register(%d) +brave.register(%d, %q) ]] ---@class pub @@ -34,6 +34,7 @@ m.braves = {} m.ability = {} m.taskQueue = {} m.taskMap = {} +m.prvtPad = {} --- ๆณจๅ†Œ้…’้ฆ†็š„ๅŠŸ่ƒฝ function m.on(name, callback) @@ -42,7 +43,8 @@ end --- ๆ‹›ๅ‹Ÿๅ‹‡่€…๏ผŒๅ‹‡่€…ไผšไปŽๅ…ฌๅ‘ŠๆฟไธŠ้ข†ๅ–ไปปๅŠก๏ผŒๅฎŒๆˆไปปๅŠกๅŽๅˆฐ็œ‹ๆฟๅจ˜ๅค„ไบคไป˜ไปปๅŠก ---@param num integer -function m.recruitBraves(num) +---@param privatePad string? +function m.recruitBraves(num, privatePad) for _ = 1, num do local id = #m.braves + 1 log.debug('Create brave:', id) @@ -55,13 +57,22 @@ function m.recruitBraves(num) DBGPORT or 11412, DBGWAIT or 'nil', (ROOT / 'debugger.lua'):string(), - id + id, + privatePad )), taskMap = {}, currentTask = nil, memory = 0, } end + if privatePad and not m.prvtPad[privatePad] then + thread.newchannel('req:' .. privatePad) + thread.newchannel('res:' .. privatePad) + m.prvtPad[privatePad] = { + req = thread.channel('req:' .. privatePad), + res = thread.channel('res:' .. privatePad), + } + end end --- ็ป™ๅ‹‡่€…ๆŽจ้€ไปปๅŠก @@ -69,7 +80,11 @@ function m.pushTask(info) if info.removed then return false end - taskPad:push(info.name, info.id, info.params) + if m.prvtPad[info.name] then + m.prvtPad[info.name].req:push(info.name, info.id, info.params) + else + taskPad:push(info.name, info.id, info.params) + end m.taskMap[info.id] = info return true end @@ -135,6 +150,19 @@ function m.task(name, params, callback) return m.pushTask(info) end +function m.reciveFromPad(pad) + local suc, id, name, result = pad:pop() + if not suc then + return false + end + if type(name) == 'string' then + m.popReport(m.braves[id], name, result) + else + m.popTask(m.braves[id], name, result) + end + return true +end + --- ๆŽฅๆ”ถๅ้ฆˆ function m.recieve(block) if block then @@ -146,14 +174,18 @@ function m.recieve(block) end else while true do - local suc, id, name, result = waiter:pop() - if not suc then - break + local ok + if m.reciveFromPad(waiter) then + ok = true end - if type(name) == 'string' then - m.popReport(m.braves[id], name, result) - else - m.popTask(m.braves[id], name, result) + for _, pad in pairs(m.prvtPad) do + if m.reciveFromPad(pad.res) then + ok = true + end + end + + if not ok then + break end end end diff --git a/script/pub/report.lua b/script/pub/report.lua index bce5309b4..5169023c7 100644 --- a/script/pub/report.lua +++ b/script/pub/report.lua @@ -23,7 +23,7 @@ end) pub.on('protoerror', function (err) log.warn('Load proto error:', err) - os.exit(true) + os.exit(0, true) end) pub.on('wakeup', function () end) diff --git a/script/service/net.lua b/script/service/net.lua index 61603d792..2019406e6 100644 --- a/script/service/net.lua +++ b/script/service/net.lua @@ -1,42 +1,39 @@ local socket = require "bee.socket" +local select = require "bee.select" +local selector = select.create() +local SELECT_READ = select.SELECT_READ +local SELECT_WRITE = select.SELECT_WRITE -local readfds = {} -local writefds = {} -local map = {} - -local function FD_SET(set, fd) - for i = 1, #set do - if fd == set[i] then - return - end +local function fd_set_read(s) + if s._flags & SELECT_READ ~= 0 then + return end - set[#set+1] = fd + s._flags = s._flags | SELECT_READ + selector:event_mod(s._fd, s._flags) end -local function FD_CLR(set, fd) - for i = 1, #set do - if fd == set[i] then - set[i] = set[#set] - set[#set] = nil - return - end +local function fd_clr_read(s) + if s._flags & SELECT_READ == 0 then + return end + s._flags = s._flags & (~SELECT_READ) + selector:event_mod(s._fd, s._flags) end -local function fd_set_read(fd) - FD_SET(readfds, fd) -end - -local function fd_clr_read(fd) - FD_CLR(readfds, fd) -end - -local function fd_set_write(fd) - FD_SET(writefds, fd) +local function fd_set_write(s) + if s._flags & SELECT_WRITE ~= 0 then + return + end + s._flags = s._flags | SELECT_WRITE + selector:event_mod(s._fd, s._flags) end -local function fd_clr_write(fd) - FD_CLR(writefds, fd) +local function fd_clr_write(s) + if s._flags & SELECT_WRITE == 0 then + return + end + s._flags = s._flags & (~SELECT_WRITE) + selector:event_mod(s._fd, s._flags) end local function on_event(self, name, ...) @@ -49,8 +46,8 @@ end local function close(self) local fd = self._fd on_event(self, "close") + selector:event_del(fd) fd:close() - map[fd] = nil end local stream_mt = {} @@ -69,7 +66,7 @@ function stream:write(data) return end if self._writebuf == "" then - fd_set_write(self._fd) + fd_set_write(self) end self._writebuf = self._writebuf .. data end @@ -79,35 +76,17 @@ end function stream:close() if not self.shutdown_r then self.shutdown_r = true - fd_clr_read(self._fd) + fd_clr_read(self) end if self.shutdown_w or self._writebuf == "" then self.shutdown_w = true - fd_clr_write(self._fd) + fd_clr_write(self) close(self) end end -function stream:update(timeout) - local fd = self._fd - local r = {fd} - local w = r - if self._writebuf == "" then - w = nil - end - local rd, wr = socket.select(r, w, timeout or 0) - if rd then - if #rd > 0 then - self:select_r() - end - if #wr > 0 then - self:select_w() - end - end -end local function close_write(self) - fd_clr_write(self._fd) + fd_clr_write(self) if self.shutdown_r then - fd_clr_read(self._fd) close(self) end end @@ -125,6 +104,7 @@ function stream:select_w() if n == nil then self.shutdown_w = true close_write(self) + elseif n == false then else self._writebuf = self._writebuf:sub(n + 1) if self._writebuf == "" then @@ -132,26 +112,43 @@ function stream:select_w() end end end +local function update_stream(s, event) + if event & SELECT_READ ~= 0 then + s:select_r() + end + if event & SELECT_WRITE ~= 0 then + s:select_w() + end +end local function accept_stream(fd) - local self = setmetatable({ + local s = setmetatable({ _fd = fd, + _flags = SELECT_READ, _event = {}, _writebuf = "", shutdown_r = false, shutdown_w = false, }, stream_mt) - map[fd] = self - fd_set_read(fd) - return self -end -local function connect_stream(self) - setmetatable(self, stream_mt) - fd_set_read(self._fd) - if self._writebuf ~= "" then - self:select_w() + selector:event_add(fd, SELECT_READ, function (event) + update_stream(s, event) + end) + return s +end +local function connect_stream(s) + setmetatable(s, stream_mt) + selector:event_del(s._fd) + if s._writebuf ~= "" then + s._flags = SELECT_READ | SELECT_WRITE + selector:event_add(s._fd, SELECT_READ | SELECT_WRITE, function (event) + update_stream(s, event) + end) + s:select_w() else - fd_clr_write(self._fd) + s._flags = SELECT_READ + selector:event_add(s._fd, SELECT_READ, function (event) + update_stream(s, event) + end) end end @@ -169,35 +166,32 @@ function listen:is_closed() end function listen:close() self.shutdown_r = true - fd_clr_read(self._fd) close(self) end -function listen:update(timeout) - local fd = self._fd - local r = {fd} - local rd = socket.select(r, nil, timeout or 0) - if rd then - if #rd > 0 then - self:select_r() - end - end -end -function listen:select_r() - local newfd = self._fd:accept() - if newfd:status() then - local news = accept_stream(newfd) - on_event(self, "accept", news) - end -end local function new_listen(fd) local s = { _fd = fd, + _flags = SELECT_READ, _event = {}, shutdown_r = false, shutdown_w = true, } - map[fd] = s - fd_set_read(fd) + selector:event_add(fd, SELECT_READ, function () + local newfd, err = fd:accept() + if not newfd then + on_event(s, "error", err) + return + end + local ok, err = newfd:status() + if not ok then + on_event(s, "error", err) + return + end + if newfd:status() then + local news = accept_stream(newfd) + on_event(s, "accept", news) + end + end) return setmetatable(s, listen_mt) end @@ -220,39 +214,27 @@ function connect:is_closed() end function connect:close() self.shutdown_w = true - fd_clr_write(self._fd) close(self) end -function connect:update(timeout) - local fd = self._fd - local w = {fd} - local rd, wr = socket.select(nil, w, timeout or 0) - if rd then - if #wr > 0 then - self:select_w() - end - end -end -function connect:select_w() - local ok, err = self._fd:status() - if ok then - connect_stream(self) - on_event(self, "connect") - else - on_event(self, "error", err) - self:close() - end -end local function new_connect(fd) local s = { _fd = fd, + _flags = SELECT_WRITE, _event = {}, _writebuf = "", shutdown_r = false, shutdown_w = false, } - map[fd] = s - fd_set_write(fd) + selector:event_add(fd, SELECT_WRITE, function () + local ok, err = fd:status() + if ok then + connect_stream(s) + on_event(s, "connect") + else + on_event(s, "error", err) + s:close() + end + end) return setmetatable(s, connect_mt) end @@ -292,18 +274,8 @@ function m.connect(protocol, ...) end function m.update(timeout) - local rd, wr = socket.select(readfds, writefds, timeout or 0) - if rd then - for i = 1, #rd do - local fd = rd[i] - local s = map[fd] - s:select_r() - end - for i = 1, #wr do - local fd = wr[i] - local s = map[fd] - s:select_w() - end + for func, event in selector:wait(timeout or 0) do + func(event) end end diff --git a/script/service/service.lua b/script/service/service.lua index 07612d7bf..b6056390b 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -13,6 +13,9 @@ local time = require 'bee.time' local fw = require 'filewatch' local furi = require 'file-uri' +require 'jsonc' +require 'json-beautify' + ---@class service local m = {} m.type = 'service' @@ -165,12 +168,13 @@ function m.eventLoop() end local function doSomething() + timer.update() pub.step(false) - if not await.step() then - return false + if await.step() then + busy() + return true end - busy() - return true + return false end local function sleep() @@ -185,10 +189,6 @@ function m.eventLoop() end while true do - if doSomething() then - goto CONTINUE - end - timer.update() if doSomething() then goto CONTINUE end @@ -211,7 +211,7 @@ function m.reportStatus() local tooltips = {} local params = { - ast = files.astCount, + ast = files.countStates(), max = files.fileCount, mem = collectgarbage('count') / 1000, } @@ -243,13 +243,38 @@ function m.testVersion() end end +function m.lockCache() + local fs = require 'bee.filesystem' + local sp = require 'bee.subprocess' + local cacheDir = string.format('%s/cache', LOGPATH) + local myCacheDir = string.format('%s/%d' + , cacheDir + , sp.get_id() + ) + fs.create_directories(fs.path(myCacheDir)) + local err + m.lockFile, err = io.open(myCacheDir .. '/.lock', 'wb') + if err then + log.error(err) + end +end + function m.start() util.enableCloseFunction() await.setErrorHandle(log.error) pub.recruitBraves(4) - proto.listen() + if COMPILECORES and COMPILECORES > 0 then + pub.recruitBraves(COMPILECORES, 'compile') + end + if SOCKET then + assert(math.tointeger(SOCKET), '`socket` must be integer') + proto.listen('socket', SOCKET) + else + proto.listen('stdio') + end m.report() m.testVersion() + m.lockCache() require 'provider' diff --git a/script/string-merger.lua b/script/string-merger.lua index b2a63f025..65896575a 100644 --- a/script/string-merger.lua +++ b/script/string-merger.lua @@ -127,6 +127,10 @@ function m.getOffsetBack(info, offset) finish = diff.finish end if not start or not finish then + if offset > diff.cstart + and offset < diff.cfinish then + return diff.finish, diff.finish + end local soff = offset - diff.cstart local pos = math.min(diff.start + soff, diff.finish) start = start or pos diff --git a/script/timer.lua b/script/timer.lua index a14cdd270..14d33f6ae 100644 --- a/script/timer.lua +++ b/script/timer.lua @@ -14,6 +14,7 @@ local curIndex = 0 local tarFrame = 0 local fwFrame = 0 local freeQueue = {} +---@type (timer|false)[][] local timer = {} local function allocQueue() @@ -97,7 +98,14 @@ local function onTick() freeQueue[#freeQueue + 1] = q end +---@class timer.manager local m = {} + +---@class timer +---@field package _onTimer? fun(self: timer) +---@field package _timeoutFrame integer +---@field package _timeout integer +---@field package _timerCount integer local mt = {} mt.__index = mt mt.type = 'timer' @@ -113,6 +121,7 @@ function mt:__call() end function mt:remove() + ---@package self._removed = true end @@ -120,7 +129,9 @@ function mt:pause() if self._removed or self._pauseRemaining then return end + ---@package self._pauseRemaining = getRemaining(self) + ---@package self._running = false local ti = self._timeoutFrame local q = timer[ti] @@ -139,6 +150,7 @@ function mt:resume() return end local timeout = self._pauseRemaining + ---@package self._pauseRemaining = nil mTimeout(self, timeout) end @@ -157,6 +169,7 @@ function mt:restart() end end end + ---@package self._running = false mTimeout(self, self._timeout) end @@ -170,21 +183,23 @@ function mt:onTimer() end function m.wait(timeout, onTimer) + local _timeout = mathMax(mathFloor(timeout * 1000.0), 1) local t = setmetatable({ - ['_timeout'] = mathMax(mathFloor(timeout * 1000.0), 1), + ['_timeout'] = _timeout, ['_onTimer'] = onTimer, ['_timerCount'] = 1, }, mt) - mTimeout(t, t._timeout) + mTimeout(t, _timeout) return t end function m.loop(timeout, onTimer) + local _timeout = mathFloor(timeout * 1000.0) local t = setmetatable({ - ['_timeout'] = mathFloor(timeout * 1000.0), + ['_timeout'] = _timeout, ['_onTimer'] = onTimer, }, mt) - mTimeout(t, t._timeout) + mTimeout(t, _timeout) return t end @@ -192,12 +207,13 @@ function m.timer(timeout, count, onTimer) if count == 0 then return m.loop(timeout, onTimer) end + local _timeout = mathFloor(timeout * 1000.0) local t = setmetatable({ - ['_timeout'] = mathFloor(timeout * 1000.0), + ['_timeout'] = _timeout, ['_onTimer'] = onTimer, ['_timerCount'] = count, }, mt) - mTimeout(t, t._timeout) + mTimeout(t, _timeout) return t end diff --git a/script/utility.lua b/script/utility.lua index 7f9d559bb..936726f9b 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -83,7 +83,7 @@ local RESERVED = { local m = {} --- ๆ‰“ๅฐ่กจ็š„็ป“ๆž„ ----@param tbl table +---@param tbl any ---@param option? table ---@return string function m.dump(tbl, option) @@ -190,38 +190,48 @@ function m.dump(tbl, option) end --- ้€’ๅฝ’ๅˆคๆ–ญAไธŽBๆ˜ฏๅฆ็›ธ็ญ‰ ----@param a any ----@param b any +---@param valueA any +---@param valueB any ---@return boolean -function m.equal(a, b) - local tp1 = type(a) - local tp2 = type(b) - if tp1 ~= tp2 then - return false - end - if tp1 == 'table' then - local mark = {} - for k, v in pairs(a) do - mark[k] = true - local res = m.equal(v, b[k]) - if not res then - return false - end +function m.equal(valueA, valueB) + local hasChecked = {} + + local function equal(a, b) + local tp1 = type(a) + local tp2 = type(b) + if tp1 ~= tp2 then + return false end - for k in pairs(b) do - if not mark[k] then - return false + if tp1 == 'table' then + if hasChecked[a] then + return true + end + hasChecked[a] = true + local mark = {} + for k, v in pairs(a) do + mark[k] = true + local res = equal(v, b[k]) + if not res then + return false + end + end + for k in pairs(b) do + if not mark[k] then + return false + end end - end - return true - elseif tp1 == 'number' then - if mathAbs(a - b) <= 1e-10 then return true + elseif tp1 == 'number' then + if mathAbs(a - b) <= 1e-10 then + return true + end + return tostring(a) == tostring(b) + else + return a == b end - return tostring(a) == tostring(b) - else - return a == b end + + return equal(valueA, valueB) end local function sortTable(tbl) @@ -283,6 +293,9 @@ end --- ่ฏปๅ–ๆ–‡ไปถ ---@param path string +---@param keepBom? boolean +---@return string? text +---@return string? errMsg function m.loadFile(path, keepBom) local f, e = ioOpen(path, 'rb') if not f then @@ -308,6 +321,8 @@ end --- ๅ†™ๅ…ฅๆ–‡ไปถ ---@param path string ---@param content string +---@return boolean ok +---@return string? errMsg function m.saveFile(path, content) local f, e = ioOpen(path, "wb") @@ -336,7 +351,10 @@ function m.counter(init, step) end --- ๆŽ’ๅบๅŽ้ๅކ ----@param t table +---@generic K, V +---@param t table +---@param sorter? fun(a: K, b: K): boolean +---@return fun(): K, V function m.sortPairs(t, sorter) local keys = {} for k in pairs(t) do @@ -354,6 +372,7 @@ end --- ๆทฑๆ‹ท่ด๏ผˆไธๅค„็†ๅ…ƒ่กจ๏ผ‰ ---@param source table ---@param target? table +---@return table function m.deepCopy(source, target) local mark = {} local function copy(a, b) @@ -376,6 +395,8 @@ function m.deepCopy(source, target) end --- ๅบๅˆ—ๅŒ– +---@param t table +---@return table function m.unpack(t) local result = {} local tid = 0 @@ -403,6 +424,8 @@ function m.unpack(t) end --- ๅๅบๅˆ—ๅŒ– +---@param t table +---@return table function m.pack(t) local cache = {} local function pack(id) @@ -515,18 +538,25 @@ function m.utf8Len(str, start, finish) return len end -function m.revertTable(t) - local len = #t +-- ๆŠŠๆ•ฐ็ป„ไธญ็š„ๅ…ƒ็ด ้กบๅบ*ๅŽŸๅœฐ*ๅ่ฝฌ +---@param arr any[] +---@return any[] +function m.revertArray(arr) + local len = #arr if len <= 1 then - return t + return arr end for x = 1, len // 2 do local y = len - x + 1 - t[x], t[y] = t[y], t[x] + arr[x], arr[y] = arr[y], arr[x] end - return t + return arr end +-- ๅˆ›ๅปบไธ€ไธชvalue-key่กจ +---@generic K, V +---@param t table +---@return table function m.revertMap(t) local nt = {} for k, v in pairs(t) do @@ -581,7 +611,7 @@ end ---้ๅކๆ–‡ๆœฌ็š„ๆฏไธ€่กŒ ---@param text string ---@param keepNL? boolean # ไฟ็•™ๆข่กŒ็ฌฆ ----@return fun():string, integer +---@return fun():string?, integer? function m.eachLine(text, keepNL) local offset = 1 local lineCount = 0 @@ -621,6 +651,11 @@ function m.eachLine(text, keepNL) end end +---@alias SortByScoreCallback fun(o: any): integer + +-- ๆŒ‰็…งๅˆ†ๆ•ฐๆŽ’ๅบ๏ผŒๅˆ†ๆ•ฐ่ถŠ้ซ˜่ถŠ้ ๅ‰ +---@param tbl any[] +---@param callbacks SortByScoreCallback | SortByScoreCallback[] function m.sortByScore(tbl, callbacks) if type(callbacks) ~= 'table' then callbacks = { callbacks } @@ -648,6 +683,16 @@ function m.sortByScore(tbl, callbacks) end) end +---@param arr any[] +---@return SortByScoreCallback +function m.sortCallbackOfIndex(arr) + ---@type table + local indexMap = m.revertMap(arr) + return function (v) + return - indexMap[v] + end +end + ---่ฃๅ‰ชๅญ—็ฌฆไธฒ ---@param str string ---@param mode? '"left"'|'"right"' @@ -730,6 +775,7 @@ function switchMT:has(name) end ---@param name string +---@param ... any ---@return ... function switchMT:__call(name, ...) local callback = self.map[name] or self._default @@ -749,6 +795,8 @@ function m.switch() end ---@param f async fun() +---@param name string +---@return any, boolean function m.getUpvalue(f, name) for i = 1, 999 do local uname, value = getupvalue(f, i) @@ -772,6 +820,9 @@ end function m.defaultTable(default) return setmetatable({}, { __index = function (t, k) + if k == nil then + return nil + end local v = default(k) t[k] = v return v @@ -782,12 +833,18 @@ function m.multiTable(count, default) local current if default then current = setmetatable({}, { __index = function (t, k) + if k == nil then + return nil + end local v = default(k) t[k] = v return v end }) else current = setmetatable({}, { __index = function (t, k) + if k == nil then + return nil + end local v = {} t[k] = v return v @@ -795,6 +852,9 @@ function m.multiTable(count, default) end for _ = 3, count do current = setmetatable({}, { __index = function (t, k) + if k == nil then + return nil + end t[k] = current return current end }) @@ -804,6 +864,7 @@ end ---@param t table ---@param sorter boolean|function +---@return any[] function m.getTableKeys(t, sorter) local keys = {} for k in pairs(t) do @@ -826,6 +887,15 @@ function m.arrayHas(array, value) return false end +function m.arrayIndexOf(array, value) + for i = 1, #array do + if array[i] == value then + return i + end + end + return nil +end + function m.arrayInsert(array, value) if not m.arrayHas(array, value) then array[#array+1] = value @@ -858,4 +928,24 @@ function m.cacheReturn(func) end end +---@param a table +---@param b table +---@return table +function m.tableMerge(a, b) + for k, v in pairs(b) do + a[k] = v + end + return a +end + +---@param a any[] +---@param b any[] +---@return any[] +function m.arrayMerge(a, b) + for i = 1, #b do + a[#a+1] = b[i] + end + return a +end + return m diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 8fe69241a..a3ed93124 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -5,21 +5,22 @@ local rpath = require 'workspace.require-path' local files = require 'files' ---@class vm local vm = require 'vm.vm' - -local LOCK = {} +local plugin = require 'plugin' ---@class parser.object ----@field _compiledNodes boolean ----@field _node vm.node ----@field _globalBase table ----@field cindex integer ----@field func parser.object ----@field operators? parser.object[] +---@field _compiledNodes boolean +---@field _node vm.node +---@field cindex integer +---@field func parser.object +---@field hideView boolean +---@field package _returns? parser.object[] +---@field package _callReturns? parser.object[] +---@field package _asCache? parser.object[] -- ่ฏฅๅ‡ฝๆ•ฐๆœ‰ๅ‰ฏไฝœ็”จ๏ผŒไผš็ป™source็ป‘ๅฎšnode๏ผ ---@param source parser.object ---@return boolean -local function bindDocs(source) +function vm.bindDocs(source) local docs = source.bindDocs if not docs then return false @@ -32,6 +33,12 @@ local function bindDocs(source) end if doc.type == 'doc.class' then vm.setNode(source, vm.compileNode(doc)) + for j = i + 1, #docs do + local overload = docs[j] + if overload.type == 'doc.overload' then + overload.overload.hideView = true + end + end return true end if doc.type == 'doc.param' then @@ -66,25 +73,30 @@ local function bindDocs(source) return false end ----@param source parser.object ----@param key any ----@param ref boolean +---@param source parser.object | vm.variable +---@param key string|vm.global|vm.ANY ---@param pushResult fun(res: parser.object, markDoc?: boolean) -local function searchFieldByLocalID(source, key, ref, pushResult) +local function searchFieldByLocalID(source, key, pushResult) local fields - if key then - fields = vm.getLocalSourcesSets(source, key) - if ref then - local gets = vm.getLocalSourcesGets(source, key) - if gets then - fields = fields or {} - for _, src in ipairs(gets) do - fields[#fields+1] = src - end - end + if key ~= vm.ANY then + if type(key) ~= 'string' then + return + end + if source.type == 'variable' then + ---@cast source vm.variable + fields = source:getSets(key) + else + ---@cast source parser.object + fields = vm.getVariableSets(source, key) end else - fields = vm.getLocalFields(source, false) + if source.type == 'variable' then + ---@cast source vm.variable + fields = source:getFields(false) + else + ---@cast source parser.object + fields = vm.getVariableFields(source, false) + end end if not fields then return @@ -92,7 +104,7 @@ local function searchFieldByLocalID(source, key, ref, pushResult) local hasMarkDoc = {} for _, src in ipairs(fields) do if src.bindDocs then - if bindDocs(src) then + if vm.bindDocs(src) then local skey = guide.getKeyName(src) if skey then hasMarkDoc[skey] = true @@ -111,16 +123,15 @@ end ---@param suri uri ---@param source parser.object ----@param key any ----@param ref boolean +---@param key string|vm.global|vm.ANY ---@param pushResult fun(res: parser.object, markDoc?: boolean) -local function searchFieldByGlobalID(suri, source, key, ref, pushResult) - local node = source._globalNode +local function searchFieldByGlobalID(suri, source, key, pushResult) + local node = vm.getGlobalNode(source) if not node then return end if node.cate == 'variable' then - if key then + if key ~= vm.ANY then if type(key) ~= 'string' then return end @@ -129,9 +140,6 @@ local function searchFieldByGlobalID(suri, source, key, ref, pushResult) 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 = vm.getGlobalFields('variable', node.name) @@ -139,33 +147,30 @@ local function searchFieldByGlobalID(suri, source, key, ref, pushResult) 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, ref, pushResult) + vm.getClassFields(suri, node, key, pushResult) end end local searchFieldSwitch = util.switch() : case 'table' - : call(function (suri, source, key, ref, pushResult) + : 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 + if key == vm.ANY or key == fieldKey then hasFiled = true pushResult(field) end end if field.type == 'tableexp' then - if key == nil + if key == vm.ANY or key == field.tindex then hasFiled = true pushResult(field) @@ -179,7 +184,7 @@ local searchFieldSwitch = util.switch() hasFiled = true pushResult(field) end - if key == nil then + if key == vm.ANY then pushResult(field) end end @@ -187,58 +192,68 @@ local searchFieldSwitch = util.switch() end) : case 'string' : case 'doc.type.string' - : call(function (suri, source, key, ref, pushResult) + : call(function (suri, source, key, pushResult) -- change to `string: stringlib` ? local stringlib = vm.getGlobal('type', 'stringlib') if stringlib then - vm.getClassFields(suri, stringlib, key, ref, pushResult) + vm.getClassFields(suri, stringlib, key, pushResult) end end) : case 'doc.type.array' - : call(function (suri, source, key, ref, pushResult) + : call(function (suri, source, key, pushResult) if type(key) == 'number' then if key < 1 or not math.tointeger(key) then return end - pushResult(source.node) + pushResult(source.node, true) end if type(key) == 'table' then if vm.isSubType(suri, key, 'integer') then - pushResult(source.node) + pushResult(source.node, true) end end end) : case 'doc.type.table' - : call(function (suri, source, key, ref, pushResult) + : call(function (suri, source, key, pushResult) + if type(key) == 'string' and key:find(vm.ID_SPLITE) then + return + end 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 + if key == vm.ANY 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) + pushResult(field, true) + end + elseif fn.type == 'doc.type.string' + or fn.type == 'doc.type.integer' + or fn.type == 'doc.type.boolean' then + if key == vm.ANY + or fn[1] == key then + pushResult(field, true) end end end end if fieldKey.type == 'doc.field.name' then - if key == nil or fieldKey[1] == key then - pushResult(field) + if key == vm.ANY or fieldKey[1] == key then + pushResult(field, true) end end end end) : case 'global' - : call(function (suri, node, key, ref, pushResult) + : call(function (suri, node, key, pushResult) if node.cate == 'variable' then - if key then + if key ~= vm.ANY then if type(key) ~= 'string' then return end @@ -247,11 +262,6 @@ local searchFieldSwitch = util.switch() for _, set in ipairs(global:getSets(suri)) do pushResult(set) end - if ref then - for _, get in ipairs(global:getGets(suri)) do - pushResult(get) - end - end end else local globals = vm.getGlobalFields('variable', node.name) @@ -259,29 +269,23 @@ local searchFieldSwitch = util.switch() for _, set in ipairs(global:getSets(suri)) do pushResult(set) end - if ref then - for _, get in ipairs(global:getGets(suri)) do - pushResult(get) - end - end end end end if node.cate == 'type' then - vm.getClassFields(suri, node, key, ref, pushResult) + vm.getClassFields(suri, node, key, pushResult) end end) - : default(function (suri, source, key, ref, pushResult) - searchFieldByLocalID(source, key, ref, pushResult) - searchFieldByGlobalID(suri, source, key, ref, pushResult) + : default(function (suri, source, key, pushResult) + searchFieldByLocalID(source, key, pushResult) + searchFieldByGlobalID(suri, source, key, pushResult) end) ---@param suri uri ---@param object vm.global ----@param key string|vm.global ----@param ref boolean +---@param key string|number|integer|boolean|vm.global|vm.ANY ---@param pushResult fun(field: vm.object, isMark?: boolean) -function vm.getClassFields(suri, object, key, ref, pushResult) +function vm.getClassFields(suri, object, key, pushResult) local mark = {} local function searchClass(class, searchedFields) @@ -291,68 +295,82 @@ function vm.getClassFields(suri, object, key, ref, pushResult) 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 = {} - local function copyToSearched() - for fieldKey in pairs(hasFounded) do - searchedFields[fieldKey] = true - hasFounded[fieldKey] = nil - end - end + local hasFounded = {} + local function copyToSearched() + for fieldKey in pairs(hasFounded) do + searchedFields[fieldKey] = true + hasFounded[fieldKey] = nil + end + end + local sets = class:getSets(suri) + for _, set in ipairs(sets) do + if set.type == 'doc.class' then + -- check ---@field for _, field in ipairs(set.fields) do local fieldKey = guide.getKeyName(field) if fieldKey then -- ---@field x boolean -> class.x - if key == nil + if key == vm.ANY or fieldKey == key then if not searchedFields[fieldKey] then pushResult(field, true) hasFounded[fieldKey] = true end end - elseif key and not hasFounded[key] then - local keyType = type(key) - if keyType == 'table' then - -- ---@field [integer] boolean -> class[integer] + goto CONTINUE + end + if key == vm.ANY then + pushResult(field, true) + goto CONTINUE + end + if hasFounded[key] then + goto CONTINUE + end + local keyType = type(key) + if keyType == 'table' then + -- ---@field [integer] boolean -> class[integer] + local fieldNode = vm.compileNode(field.field) + if vm.isSubType(suri, key.name, fieldNode) then + local nkey = '|' .. key.name + if not searchedFields[nkey] then + pushResult(field, true) + hasFounded[nkey] = true + end + end + else + local keyObject + if keyType == 'number' then + if math.tointeger(key) then + keyObject = { type = 'integer', [1] = key } + else + keyObject = { type = 'number', [1] = key } + end + elseif keyType == 'boolean' + or keyType == 'string' then + keyObject = { type = keyType, [1] = key } + end + if keyObject and field.field.type ~= 'doc.field.name' then + -- ---@field [integer] boolean -> class[1] local fieldNode = vm.compileNode(field.field) - if vm.isSubType(suri, key.name, fieldNode) then - local nkey = '|' .. key.name + if vm.isSubType(suri, keyObject, fieldNode) then + local nkey = '|' .. keyType if not searchedFields[nkey] then pushResult(field, true) hasFounded[nkey] = true end end - else - local typeName - if keyType == 'number' then - if math.tointeger(key) then - typeName = 'integer' - else - typeName = 'number' - end - elseif keyType == 'boolean' - or keyType == 'string' then - typeName = keyType - end - if typeName and field.field.type ~= 'doc.field.name' then - -- ---@field [integer] boolean -> class[1] - local fieldNode = vm.compileNode(field.field) - if vm.isSubType(suri, typeName, fieldNode) then - local nkey = '|' .. typeName - if not searchedFields[nkey] then - pushResult(field, true) - hasFounded[nkey] = true - end - end - end end end + ::CONTINUE:: end - copyToSearched() + end + end + copyToSearched() + + for _, set in ipairs(sets) do + if set.type == 'doc.class' then -- check local field and global field if not searchedFields[key] and set.bindSource then local src = set.bindSource @@ -375,19 +393,60 @@ function vm.getClassFields(suri, object, key, ref, pushResult) end end end - copyToSearched() - searchFieldSwitch(src.type, suri, src, key, ref, function (field) + if src.value + and src.value.type == 'select' + and src.value.vararg.type == 'call' then + local func = src.value.vararg.node + local args = src.value.vararg.args + if func.special == 'setmetatable' + and args + and args[1] + and args[1].type == 'table' then + searchFieldSwitch('table', suri, args[1], key, function (field) + local fieldKey = guide.getKeyName(field) + if fieldKey then + if not searchedFields[fieldKey] + and guide.isAssign(field) then + hasFounded[fieldKey] = true + pushResult(field, true) + end + end + end) + end + end + end + end + end + copyToSearched() + + for _, set in ipairs(sets) do + if set.type == 'doc.class' then + if not searchedFields[key] and set.bindSource then + local src = set.bindSource + searchFieldSwitch(src.type, suri, src, key, function (field) local fieldKey = guide.getKeyName(field) if fieldKey and not searchedFields[fieldKey]then if not searchedFields[fieldKey] - and guide.isSet(field) then - hasFounded[fieldKey] = true + and guide.isAssign(field) + and field.value then + if vm.getVariableID(field) + and vm.getVariableID(field) == vm.getVariableID(field.value) then + elseif vm.getGlobalNode(src) + and vm.getGlobalNode(src) == vm.getGlobalNode(field.value) then + else + hasFounded[fieldKey] = true + end pushResult(field, true) end end end) - copyToSearched() end + end + end + copyToSearched() + + for _, set in ipairs(sets) do + if set.type == 'doc.class' then -- look into extends(if field not found) if not searchedFields[key] and set.extends then for _, extend in ipairs(set.extends) do @@ -398,15 +457,15 @@ function vm.getClassFields(suri, object, key, ref, pushResult) end end end - copyToSearched() end end end + copyToSearched() end local function searchGlobal(class) if class.cate == 'type' and class.name == '_G' then - if key == nil then + if key == vm.ANY then local sets = vm.getGlobalSets(suri, 'variable') for _, set in ipairs(sets) do pushResult(set) @@ -426,69 +485,6 @@ function vm.getClassFields(suri, object, key, ref, pushResult) searchGlobal(object) end ----@class parser.object ----@field _sign vm.sign|false - ----@param source parser.object ----@return vm.sign|false -local function getObjectSign(source) - if source._sign ~= nil then - return source._sign - end - source._sign = false - if source.type == 'function' then - if not source.bindDocs then - return false - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.generic' then - if not source._sign then - source._sign = vm.createSign() - 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 = vm.createSign() - if source.type == 'doc.type.function' then - for _, arg in ipairs(source.args) do - if arg.extends then - local argNode = vm.compileNode(arg.extends) - if arg.optional then - argNode:addOptional() - end - source._sign:addSign(argNode) - else - source._sign:addSign(vm.createNode()) - end - end - end - end - return source._sign -end - ---@param func parser.object ---@param index integer ---@return (parser.object|vm.generic)? @@ -498,11 +494,13 @@ function vm.getReturnOfFunction(func, index) func._returns = {} end if not func._returns[index] then + ---@diagnostic disable-next-line: missing-fields func._returns[index] = { type = 'function.return', parent = func, returnIndex = index, } + vm.compileNode(func._returns[index]) end return func._returns[index] end @@ -516,7 +514,7 @@ function vm.getReturnOfFunction(func, index) return nil end end - local sign = getObjectSign(func) + local sign = vm.getSign(func) if not sign then return rtn end @@ -535,7 +533,7 @@ local function getReturnOfSetMetaTable(args) node:merge(vm.compileNode(tbl)) end if mt then - vm.compileByParentNode(mt, '__index', false, function (src) + vm.compileByParentNodeAll(mt, '__index', function (src) for n in vm.compileNode(src):eachObject() do if n.type == 'global' or n.type == 'local' @@ -546,6 +544,8 @@ local function getReturnOfSetMetaTable(args) end end) end + --่ฟ‡ๆปคnil + node:remove 'nil' return node end @@ -577,7 +577,7 @@ local function matchCall(source) if needRemove then local newNode = myNode:copy() newNode:removeNode(needRemove) - newNode:setData('originNode', myNode) + newNode.originNode = myNode vm.setNode(source, newNode, true) end end @@ -592,6 +592,7 @@ local function getReturn(func, index, args) end if not func._callReturns[index] then local call = func.parent + ---@diagnostic disable-next-line: missing-fields func._callReturns[index] = { type = 'call.return', parent = call, @@ -607,7 +608,7 @@ end ---@param source parser.object ---@return boolean -local function bindAs(source) +function vm.bindAs(source) local root = guide.getRoot(source) local docs = root.docs if not docs then @@ -651,17 +652,19 @@ local function bindAs(source) local doc = ases[index] if doc and doc.touch == source.finish then - vm.setNode(source, vm.compileNode(doc.as), true) + local asNode = vm.compileNode(doc.as) + vm.setNode(source, asNode, true) return true end return false end ----@param source parser.object ----@param key? string|vm.global ----@param pushResult fun(source: parser.object) -function vm.compileByParentNode(source, key, ref, pushResult) +---@param source parser.object | vm.variable +---@param key string|vm.global|vm.ANY +---@return parser.object[] docedResults +---@return parser.object[] commonResults +function vm.getNodesOfParentNode(source, key) local parentNode = vm.compileNode(source) local docedResults = {} local commonResults = {} @@ -686,7 +689,7 @@ function vm.compileByParentNode(source, key, ref, pushResult) and not guide.isBasicType(node.name) ) or guide.isLiteral(node) then - searchFieldSwitch(node.type, suri, node, key, ref, function (res, markDoc) + searchFieldSwitch(node.type, suri, node, key, function (res, markDoc) if mark[res] then return end @@ -701,7 +704,7 @@ function vm.compileByParentNode(source, key, ref, pushResult) end if not next(mark) then - searchFieldByLocalID(source, key, ref, function (res, markDoc) + searchFieldByLocalID(source, key, function (res, markDoc) if mark[res] then return end @@ -714,18 +717,43 @@ function vm.compileByParentNode(source, key, ref, pushResult) end) end + return docedResults, commonResults +end + +-- ้ๅކๆ‰€ๆœ‰ๅญ—ๆฎต๏ผˆๆŒ‰็…งไผ˜ๅ…ˆ็บง๏ผ‰ +---@param source parser.object | vm.variable +---@param key string|vm.global|vm.ANY +---@param pushResult fun(source: parser.object) +function vm.compileByParentNode(source, key, pushResult) + local docedResults, commonResults = vm.getNodesOfParentNode(source, key) + if #docedResults > 0 then for _, res in ipairs(docedResults) do pushResult(res) end end - if #docedResults == 0 or key == nil then + if #docedResults == 0 or key == vm.ANY then for _, res in ipairs(commonResults) do pushResult(res) end end end +-- ้ๅކๆ‰€ๆœ‰ๅญ—ๆฎต๏ผˆๆ— ่ง†ไผ˜ๅ…ˆ็บง๏ผ‰ +---@param source parser.object | vm.variable +---@param key string|vm.global|vm.ANY +---@param pushResult fun(source: parser.object) +function vm.compileByParentNodeAll(source, key, pushResult) + local docedResults, commonResults = vm.getNodesOfParentNode(source, key) + + for _, res in ipairs(docedResults) do + pushResult(res) + end + for _, res in ipairs(commonResults) do + pushResult(res) + end +end + ---@param list parser.object[] ---@param index integer ---@return vm.node @@ -751,6 +779,11 @@ function vm.selectNode(list, index) if not exp then return vm.createNode(vm.declareGlobal('type', 'nil')), nil end + + if vm.bindDocs(list) then + return vm.compileNode(list), exp + end + ---@type vm.node? local result if exp.type == 'call' then @@ -841,6 +874,7 @@ end ---@param fixIndex integer ---@param myIndex integer local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) + ---@type integer?, table? local eventIndex, eventMap if call.args then for i = 1, 2 do @@ -856,49 +890,69 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) end end - for n in callNode:eachObject() do - if n.type == 'function' then - ---@cast n parser.object - local sign = getObjectSign(n) + ---@param n parser.object + local function dealDocFunc(n) + 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 - if fn.type == 'doc.type.function' then - ---@cast fn parser.object - if sign then - local generic = vm.createGeneric(fn, sign) - local args = {} - for i = fixIndex + 1, myIndex - 1 do - args[#args+1] = call.args[i] - end - fn = generic:resolve(guide.getUri(call), args) - end - end vm.setNode(arg, fn) end end end end - if n.type == 'doc.type.function' then - ---@cast n parser.object - 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 + + ---@param n parser.object + local function dealFunction(n) + local sign = vm.getSign(n) + local farg = getFuncArg(n, myIndex) + if farg then + for fn in vm.compileNode(farg):eachObject() do + if isValidCallArgNode(arg, fn) then + if fn.type == 'doc.type.function' then + ---@cast fn parser.object + if sign then + local generic = vm.createGeneric(fn, sign) + local args = {} + for i = fixIndex + 1, myIndex - 1 do + args[#args+1] = call.args[i] + end + local resolvedNode = generic:resolve(guide.getUri(call), args) + vm.setNode(arg, resolvedNode) + goto CONTINUE end end + vm.setNode(arg, fn) + ::CONTINUE:: + end + end + end + end + + for n in callNode:eachObject() do + if n.type == 'function' then + ---@cast n parser.object + dealFunction(n) + elseif n.type == 'doc.type.function' then + ---@cast n parser.object + dealDocFunc(n) + elseif n.type == 'global' and n.cate == 'type' then + ---@cast n vm.global + local overloads = vm.getOverloadsByTypeName(n.name, guide.getUri(arg)) + if overloads then + for _, func in ipairs(overloads) do + dealDocFunc(func) end end end @@ -937,68 +991,121 @@ function vm.compileCallArg(arg, call, index) end ---@class parser.object ----@field _iterator? table ----@field _iterArgs? table ----@field _iterVars? table +---@field package _iterator? table +---@field package _iterArgs? table +---@field package _iterVars? table ---@param source parser.object -local function compileForVars(source) - if source._iterator then - return - end +---@param target parser.object +---@return boolean +local function compileForVars(source, target) if not source.exps then - return + return false + end + -- for k, v in pairs(t) do + --> for k, v in iterator, status, initValue do + --> local k, v = iterator(status, initValue) + if not source._iterator then + source._iterator = { + type = 'dummyfunc', + parent = source, + } + source._iterArgs = {{},{}} + source._iterVars = {} end - -- for k, v in pairs(t) do - --> for k, v in iterator, status, initValue do - --> local k, v = iterator(status, initValue) - source._iterator = { - type = 'dummyfunc', - parent = source, - } - source._iterArgs = {{},{}} - source._iterVars = {} -- iterator - selectNode(source._iterator, source.exps, 1) + if not vm.getNode(source._iterator) then + selectNode(source._iterator, source.exps, 1) + end -- status - selectNode(source._iterArgs[1], source.exps, 2) + if not vm.getNode(source._iterArgs[1]) then + selectNode(source._iterArgs[1], source.exps, 2) + end -- initValue - selectNode(source._iterArgs[2], source.exps, 3) + if not vm.getNode(source._iterArgs[2]) then + 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) - node:removeOptional() - source._iterVars[loc] = node + if loc == target then + local node = getReturn(source._iterator, i, source._iterArgs) + node:removeOptional() + vm.setNode(loc, node) + return true + end end end + return false +end + +---@param func parser.object +---@param source parser.object +local function compileFunctionParam(func, source) + -- local call ---@type fun(f: fun(x: number));call(function (x) end) --> x -> number + local funcNode = vm.compileNode(func) + for n in funcNode:eachObject() do + if n.type == 'doc.type.function' then + for index, arg in ipairs(n.args) do + if func.args[index] == source then + local argNode = vm.compileNode(arg) + for an in argNode:eachObject() do + if an.type ~= 'doc.generic.name' then + vm.setNode(source, an) + end + end + return true + end + end + end + end + + local derviationParam = config.get(guide.getUri(func), 'Lua.type.inferParamType') + if derviationParam and func.parent.type == 'local' and func.parent.ref then + local refs = func.parent.ref + local found + for _, ref in ipairs(refs) do + if ref.parent.type ~= 'call' then + goto continue + end + local caller = ref.parent + if not caller.args then + goto continue + end + for index, arg in ipairs(source.parent) do + if arg == source then + local callerArg = caller.args[index] + if callerArg then + vm.setNode(source, vm.compileNode(callerArg)) + finded = true + end + end + end + ::continue:: + end + return finded + end end ---@param source parser.object local function compileLocal(source) - vm.setNode(source, source) + local myNode = vm.setNode(source, source) local hasMarkDoc if source.bindDocs then - hasMarkDoc = bindDocs(source) + hasMarkDoc = vm.bindDocs(source) end local hasMarkParam - if source.type == 'self' and not hasMarkDoc then - hasMarkParam = true - if source.parent.type == 'callargs' then - -- obj:func(...) - if source.parent.parent and source.parent.parent.node and source.parent.parent.node.node then - vm.setNode(source, vm.compileNode(source.parent.parent.node.node)) - end - else - -- function obj:func(...) - if source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node then - vm.setNode(source, vm.compileNode(source.parent.parent.parent.node)) - end + if not hasMarkDoc then + local selfNode = guide.getSelfNode(source) + if selfNode then + hasMarkParam = true + vm.setNode(source, vm.compileNode(selfNode)) + myNode:remove 'function' end - vm.getNode(source):remove 'function' end local hasMarkValue - if not hasMarkDoc and source.value then + if (not hasMarkDoc and source.value) + or (source.value and source.value.type == 'table') then hasMarkValue = true if source.value.type == 'table' then vm.setNode(source, source.value) @@ -1006,17 +1113,7 @@ local function compileLocal(source) vm.setNode(source, vm.compileNode(source.value)) end end - if not hasMarkValue and not hasMarkValue then - if source.ref then - for _, ref in ipairs(source.ref) do - if ref.type == 'setlocal' - and ref.value - and ref.value.type == 'function' then - vm.setNode(source, vm.compileNode(ref.value)) - end - end - end - end + -- function x.y(self, ...) --> function x:y(...) if source[1] == 'self' and not hasMarkDoc @@ -1030,47 +1127,89 @@ local function compileLocal(source) 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 - local argNode = vm.compileNode(arg) - for an in argNode:eachObject() do - if an.type ~= 'doc.generic.name' then - vm.setNode(source, an) - end - end - hasDocArg = true - end - end - end - end + local vmPlugin = plugin.getVmPlugin(guide.getUri(source)) + local hasDocArg = vmPlugin and vmPlugin.OnCompileFunctionParam(compileFunctionParam, func, source) + or compileFunctionParam(func, source) if not hasDocArg then vm.setNode(source, vm.declareGlobal('type', 'any')) end end + -- for x in ... do if source.parent.type == 'in' then - compileForVars(source.parent) - local keyNode = source.parent._iterVars and source.parent._iterVars[source] - if keyNode then - vm.setNode(source, keyNode) - end + compileForVars(source.parent, source) + hasMarkDoc = true end -- for x = ... do if source.parent.type == 'loop' then if source.parent.loc == source then - if bindDocs(source) then + if vm.bindDocs(source) then return end vm.setNode(source, vm.declareGlobal('type', 'integer')) + hasMarkDoc = true end end - vm.getNode(source):setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue) + if not hasMarkDoc + and not hasMarkValue + and source.ref then + local firstSet + local myFunction = guide.getParentFunction(source) + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + firstSet = ref + break + end + if ref.type == 'getlocal' then + if guide.getParentFunction(ref) == myFunction then + break + end + end + end + if firstSet + and guide.getBlock(firstSet) == guide.getBlock(source) then + vm.setNode(source, vm.compileNode(firstSet)) + end + end + + if source.value + and source.value.type == 'nil' + and not myNode:hasKnownType() then + vm.setNode(source, vm.compileNode(source.value)) + end + + myNode.hasDefined = hasMarkDoc or hasMarkParam or hasMarkValue +end + +---@param source parser.object +---@param mfunc parser.object +---@param index integer +---@param args parser.object[] +local function bindReturnOfFunction(source, mfunc, index, args) + local returnObject = vm.getReturnOfFunction(mfunc, index) + if not returnObject then + return + end + local returnNode = vm.compileNode(returnObject) + for rnode in returnNode:eachObject() do + if rnode.type == 'generic' then + returnNode = rnode:resolve(guide.getUri(source), args) + break + end + end + if returnNode then + for rnode in returnNode:eachObject() do + -- TODO: narrow type + if rnode.type ~= 'doc.generic.name' then + vm.setNode(source, rnode) + end + end + if returnNode:isOptional() then + vm.getNode(source):addOptional() + end + end end local compilerSwitch = util.switch() @@ -1087,6 +1226,9 @@ local compilerSwitch = util.switch() end) : case 'table' : call(function (source) + if vm.bindAs(source) then + return + end vm.setNode(source, source) if source.parent.type == 'callargs' then @@ -1094,6 +1236,16 @@ local compilerSwitch = util.switch() vm.compileCallArg(source, call) end + if source.parent.type == 'return' then + local myIndex = util.arrayIndexOf(source.parent, source) + ---@cast myIndex -? + local parentNode = vm.selectNode(source.parent, myIndex) + if not parentNode:isEmpty() then + vm.setNode(source, parentNode) + return + end + end + if source.parent.type == 'setglobal' or source.parent.type == 'local' or source.parent.type == 'setlocal' @@ -1120,6 +1272,8 @@ local compilerSwitch = util.switch() : call(function (source) vm.setNode(source, source) + local parent = source.parent + if source.bindDocs then for _, doc in ipairs(source.bindDocs) do if doc.type == 'doc.overload' then @@ -1129,14 +1283,36 @@ local compilerSwitch = util.switch() end -- table.sort(string[], function () end) - if source.parent.type == 'callargs' then - local call = source.parent.parent + if parent.type == 'callargs' then + local call = parent.parent vm.compileCallArg(source, call) end + + -- function f() return function () end end + if parent.type == 'return' then + for i, ret in ipairs(parent) do + if ret == source then + local func = guide.getParentFunction(parent) + if func then + local returnObj = vm.getReturnOfFunction(func, i) + if returnObj then + vm.setNode(source, vm.compileNode(returnObj)) + end + end + break + end + end + end + + -- { f = function () end } + if guide.isAssign(parent) + and parent.value == source then + vm.setNode(source, vm.compileNode(parent)) + end end) : case 'paren' : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then return end if source.exp then @@ -1145,88 +1321,42 @@ local compilerSwitch = util.switch() end) : case 'local' : case 'self' + ---@async ---@param source parser.object : call(function (source) compileLocal(source) - local refs = source.ref - if not refs then - return - end - - local hasMark = vm.getNode(source):getData 'hasDefined' - - vm.launchRunner(source, function (src, node) - if src.type == 'setlocal' then - if src.bindDocs then - for _, doc in ipairs(src.bindDocs) do - if doc.type == 'doc.type' then - vm.setNode(src, vm.compileNode(doc), true) - return vm.getNode(src) - end - end - end - if src.value then - if src.value.type == 'table' then - vm.setNode(src, vm.createNode(src.value)) - vm.setNode(src, node:copy():asTable()) - else - local function clearLockedNode(child) - if not child then - return - end - if child.type == 'function' then - return - end - if child.type == 'setlocal' - or child.type == 'getlocal' then - if child.node == source then - return - end - end - if LOCK[child] then - vm.removeNode(child) - end - guide.eachChild(child, clearLockedNode) - end - clearLockedNode(src.value) - vm.setNode(src, vm.compileNode(src.value), true) - end - else - vm.setNode(src, node, true) - end - return vm.getNode(src) - elseif src.type == 'getlocal' then - if bindAs(src) then - return - end - vm.setNode(src, node, true) - matchCall(src) - end - end) - - if not hasMark then - local parentFunc = guide.getParentFunction(source) - for _, ref in ipairs(source.ref) do - if ref.type == 'setlocal' - and guide.getParentFunction(ref) == parentFunc then - local refNode = vm.getNode(ref) - if refNode then - vm.setNode(source, refNode) - end - end - end - end end) : case 'setlocal' : call(function (source) - vm.compileNode(source.node) + if vm.bindDocs(source) then + return + end + local locNode = vm.compileNode(source.node) + if not source.value then + vm.setNode(source, locNode) + return + end + local valueNode = vm.compileNode(source.value) + vm.setNode(source, valueNode) + if locNode.hasDefined + and guide.isLiteral(source.value) then + vm.setNode(source, locNode) + vm.getNode(source):narrow(guide.getUri(source), source.value.type) + else + vm.setNode(source, valueNode) + end end) : case 'getlocal' + ---@async : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then + return + end + local node = vm.traceNode(source) + if not node then return end - vm.compileNode(source.node) + vm.setNode(source, node, true) end) : case 'setfield' : case 'setmethod' @@ -1235,7 +1365,10 @@ local compilerSwitch = util.switch() : case 'getmethod' : case 'getindex' : call(function (source) - if guide.isGet(source) and bindAs(source) then + if guide.isGet(source) and vm.bindAs(source) then + return + end + if vm.bindDocs(source) then return end ---@type (string|vm.node)? @@ -1246,6 +1379,7 @@ local compilerSwitch = util.switch() if key == nil then return end + if type(key) == 'table' then ---@cast key vm.node local uri = guide.getUri(source) @@ -1256,56 +1390,47 @@ local compilerSwitch = util.switch() for k in key:eachObject() do if k.type == 'global' and k.cate == 'type' then ---@cast k vm.global - vm.compileByParentNode(source.node, k, false, function (src) + vm.compileByParentNode(source.node, k, function (src) vm.setNode(source, vm.compileNode(src)) - if src.value then - vm.setNode(source, vm.compileNode(src.value)) - end end) end end else ---@cast key string - vm.compileByParentNode(source.node, key, false, function (src) - if src.value then - if bindDocs(src) then - vm.setNode(source, vm.compileNode(src)) - elseif src.value.type ~= 'nil' then - vm.setNode(source, vm.compileNode(src.value)) - local node = vm.getNode(src) - if node then - vm.setNode(source, node) - end - end - else - vm.setNode(source, vm.compileNode(src)) + vm.compileByParentNode(source.node, key, function (src) + vm.setNode(source, vm.compileNode(src)) + if src == source and source.value and source.value.type ~= 'nil' then + vm.setNode(source, vm.compileNode(source.value)) end end) end end) : case 'setglobal' : call(function (source) + if vm.bindDocs(source) then + return + end if source.node[1] ~= '_ENV' then return end - local key = guide.getKeyName(source) - vm.compileByParentNode(source.node, key, false, function (src) - if src.type == 'doc.type.field' - or src.type == 'doc.field' then - vm.setNode(source, vm.compileNode(src)) - end - end) + if not source.value then + return + end + vm.setNode(source, vm.compileNode(source.value)) end) : case 'getglobal' : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then return end if source.node[1] ~= '_ENV' then return end local key = guide.getKeyName(source) - vm.compileByParentNode(source.node, key, false, function (src) + if not key then + return + end + vm.compileByParentNode(source.node, key, function (src) vm.setNode(source, vm.compileNode(src)) end) end) @@ -1314,21 +1439,40 @@ local compilerSwitch = util.switch() : call(function (source) local hasMarkDoc if source.bindDocs then - hasMarkDoc = bindDocs(source) + hasMarkDoc = vm.bindDocs(source) end + local key = guide.getKeyName(source) if not hasMarkDoc then - vm.compileByParentNode(source.node, guide.getKeyName(source), false, function (src) + if key then + vm.compileByParentNode(source.node, key, function (src) + if src.type == 'doc.field' + or src.type == 'doc.type.field' + or src.type == 'doc.type.name' then + hasMarkDoc = true + vm.setNode(source, vm.compileNode(src)) + end + end) + end + end + + if not hasMarkDoc and source.type == 'tableindex' then + vm.compileByParentNode(source.node, vm.ANY, function (src) if src.type == 'doc.field' or src.type == 'doc.type.field' then - hasMarkDoc = true - vm.setNode(source, vm.compileNode(src)) + if vm.isSubType(guide.getUri(source), vm.compileNode(source.index), vm.compileNode(src.field or src.name)) then + hasMarkDoc = true + vm.setNode(source, vm.compileNode(src)) + end end end) end - if not hasMarkDoc and source.value then - vm.setNode(source, vm.compileNode(source.value)) + if source.value then + if not hasMarkDoc + or (type(key) == 'string' and util.stringStartWith(key, '__')) then + vm.setNode(source, vm.compileNode(source.value)) + end end end) @@ -1339,15 +1483,20 @@ local compilerSwitch = util.switch() end) : case 'tableexp' : call(function (source) - if (source.parent.type == 'table') then - local node = vm.compileNode(source.parent) - for n in node:eachObject() do - if n.type == 'doc.type.array' then - vm.setNode(source, vm.compileNode(n.node)) - end + local hasMarkDoc + vm.compileByParentNode(source.parent, source.tindex, function (src) + if src.type == 'doc.field' + or src.type == 'doc.type.field' + or src.type == 'doc.type.name' + or src.type == 'doc.type' + or guide.isLiteral(src) then + hasMarkDoc = true + vm.setNode(source, vm.compileNode(src)) end + end) + if not hasMarkDoc then + vm.setNode(source, vm.compileNode(source.value)) end - vm.setNode(source, vm.compileNode(source.value)) end) : case 'function.return' ---@param source parser.object @@ -1356,7 +1505,7 @@ local compilerSwitch = util.switch() local index = source.returnIndex local hasMarkDoc if func.bindDocs then - local sign = getObjectSign(func) + local sign = vm.getSign(func) local lastReturn for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.return' then @@ -1364,6 +1513,10 @@ local compilerSwitch = util.switch() lastReturn = rtn if rtn.returnIndex == index then hasMarkDoc = true + source.comment = doc.comment + if rtn.name then + source.name = rtn.name[1] + end local hasGeneric if sign then guide.eachSourceType(rtn, 'doc.generic.name', function (src) @@ -1371,7 +1524,7 @@ local compilerSwitch = util.switch() end) end if hasGeneric then - ---@cast sign -false + ---@cast sign -? vm.setNode(source, vm.createGeneric(rtn, sign)) else vm.setNode(source, vm.compileNode(rtn)) @@ -1428,7 +1581,7 @@ local compilerSwitch = util.switch() : case 'call.return' ---@param source parser.object : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then return end local func = source.func @@ -1470,6 +1623,21 @@ local compilerSwitch = util.switch() return end if func.special == 'require' then + if index == 2 then + local uri = guide.getUri(source) + local version = config.get(uri, 'Lua.runtime.version') + if version == 'Lua 5.3' + or version == 'Lua 5.4' then + vm.setNode(source, vm.declareGlobal('type', 'unknown')) + else + vm.setNode(source, vm.declareGlobal('type', 'nil')) + end + return + end + if index >= 3 then + vm.setNode(source, vm.declareGlobal('type', 'nil')) + return + end if not args then return end @@ -1495,28 +1663,17 @@ local compilerSwitch = util.switch() end local funcNode = vm.compileNode(func) ---@type vm.node? - for mfunc in funcNode:eachObject() do - if mfunc.type == 'function' - or mfunc.type == 'doc.type.function' then - ---@cast mfunc parser.object - local returnObject = vm.getReturnOfFunction(mfunc, 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 - vm.setNode(source, rnode) - end - end - if returnNode:isOptional() then - vm.getNode(source):addOptional() + for nd in funcNode:eachObject() do + if nd.type == 'function' + or nd.type == 'doc.type.function' then + ---@cast nd parser.object + bindReturnOfFunction(source, nd, index, args) + elseif nd.type == 'global' and nd.cate == 'type' then + ---@cast nd vm.global + for _, set in ipairs(nd:getSets(guide.getUri(source))) do + if set.type == 'doc.class' then + for _, overload in ipairs(set.calls) do + bindReturnOfFunction(source, overload.overload, index, args) end end end @@ -1541,7 +1698,7 @@ local compilerSwitch = util.switch() if not node then return end - if node:isEmpty() then + if not node:isTyped() then node = vm.runOperator('call', vararg.node) or node end vm.setNode(source, node) @@ -1562,7 +1719,7 @@ local compilerSwitch = util.switch() if not node then return end - if node:isEmpty() then + if not node:isTyped() then node = vm.runOperator('call', source.node) or node end vm.setNode(source, node) @@ -1583,6 +1740,30 @@ local compilerSwitch = util.switch() : call(function (source) vm.setNode(source, source) end) + : case 'doc.type.name' + : call(function (source) + if source[1] == 'self' then + local state = guide.getDocState(source) + if state.type == 'doc.return' + or state.type == 'doc.param' then + local func = state.bindSource + if func and func.type == 'function' then + local node = guide.getFunctionSelfNode(func) + if node then + vm.setNode(source, vm.compileNode(node)) + return + end + end + elseif state.type == 'doc.field' + or state.type == 'doc.overload' then + local class = state.class + if class then + vm.setNode(source, vm.compileNode(class)) + return + end + end + end + end) : case 'doc.generic.name' : call(function (source) vm.setNode(source, source) @@ -1603,8 +1784,8 @@ local compilerSwitch = util.switch() 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) + if vm.getGeneric(ext) then + local resolved = vm.getGeneric(ext):resolve(uri, source.signs) vm.setNode(source, resolved) end end @@ -1612,14 +1793,15 @@ local compilerSwitch = util.switch() end end if set.type == 'doc.alias' then - if set.extends._generic then - local resolved = set.extends._generic:resolve(uri, source.signs) + if vm.getGeneric(set.extends) then + local resolved = vm.getGeneric(set.extends):resolve(uri, source.signs) vm.setNode(source, resolved) end end end end) : case 'doc.class.name' + : case 'doc.alias.name' : call(function (source) vm.setNode(source, vm.compileNode(source.parent)) end) @@ -1638,6 +1820,10 @@ local compilerSwitch = util.switch() end vm.setNode(source, fieldNode) end) + : case 'doc.field.name' + : call(function (source) + vm.setNode(source, source) + end) : case 'doc.type.field' : call(function (source) if not source.extends then @@ -1681,13 +1867,6 @@ local compilerSwitch = util.switch() : call(function (source) vm.setNode(source, vm.compileNode(source.overload)) end) - : case 'doc.see.name' - : call(function (source) - local type = vm.getGlobal('type', source[1]) - if type then - vm.setNode(source, type) - end - end) : case 'doc.type.arg' : call(function (source) if source.extends then @@ -1701,7 +1880,7 @@ local compilerSwitch = util.switch() end) : case 'unary' : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then return end if not source[1] then @@ -1711,7 +1890,7 @@ local compilerSwitch = util.switch() end) : case 'binary' : call(function (source) - if bindAs(source) then + if vm.bindAs(source) then return end if not source[1] or not source[2] then @@ -1719,102 +1898,135 @@ local compilerSwitch = util.switch() end vm.binarySwitch(source.op.type, source) end) + : case 'globalbase' + : call(function (source) + ---@type vm.global + local global = source.global + local uri = guide.getUri(source) + vm.setNode(source, global) + if global.cate == 'variable' then + for luri, link in pairs(global.links) do + local firstSet = link.sets[1] + if firstSet then + local setNode = vm.compileNode(firstSet) + vm.setNode(source, setNode) + if vm.isMetaFile(luri) then + for i = 2, #link.sets do + setNode = vm.compileNode(link.sets[i]) + vm.setNode(source, setNode) + 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 vm.getGeneric(ext) then + vm.setNode(source, vm.compileNode(ext)) + end + end + end + end + end + if set.type == 'doc.alias' then + if not vm.getGeneric(set.extends) then + vm.setNode(source, vm.compileNode(set.extends)) + end + end + end + end + end) + : case 'variable' + ---@param variable vm.variable + : call(function (variable) + if variable == vm.getVariable(variable.base) then + vm.setNode(variable, vm.compileNode(variable.base)) + return + end + end) + : case 'global' + : case 'generic' + : call(function (source) + vm.setNode(source, source) + end) ---@param source parser.object local function compileByNode(source) compilerSwitch(source.type, source) end ----@param source parser.object -local function compileByGlobal(source) - local global = source._globalNode - if not global then - return - end - ---@cast source parser.object - 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 - ---@type vm.node - globalNode = vm.createNode(global) - vm.setNode(root._globalBase[name], globalNode, true) - vm.setNode(source, globalNode, true) - - -- TODO:don't mix - --local sets = global.links[uri].sets or {} - --local gets = global.links[uri].gets or {} - --for _, set in ipairs(sets) do - -- vm.setNode(set, globalNode, true) - --end - --for _, get in ipairs(gets) do - -- vm.setNode(get, globalNode, true) - --end - - if global.cate == 'variable' then - local hasMarkDoc - for _, set in ipairs(global:getSets(uri)) do - if set.bindDocs and set.parent.type == 'main' then - if bindDocs(set) then - globalNode:merge(vm.compileNode(set)) - hasMarkDoc = true - end - if vm.getNode(set) then - globalNode:merge(vm.compileNode(set)) - end - end +local nodeSwitch;nodeSwitch = util.switch() + : case 'field' + : case 'method' + : call(function (source, lastKey, pushResult) + return nodeSwitch(source.parent.type, source.parent, lastKey, pushResult) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source, lastKey, pushResult) + local parentNode = vm.compileNode(source.node) + local uri = guide.getUri(source) + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return end - -- Set all globals node first to avoid recursive - for _, set in ipairs(global:getSets(uri)) do - vm.setNode(set, globalNode, true) + if lastKey then + key = key .. vm.ID_SPLITE .. lastKey end - for _, set in ipairs(global:getSets(uri)) do - if set.value and set.value.type ~= 'nil' and set.parent.type == 'main' then - if not hasMarkDoc or guide.isLiteral(set.value) then - globalNode:merge(vm.compileNode(set.value)) - end - end + for pn in parentNode:eachObject() do + searchFieldSwitch(pn.type, uri, pn, key, pushResult) end - for _, set in ipairs(global:getSets(uri)) do - vm.setNode(set, globalNode, true) + return key, source.node + end) + : case 'tableindex' + : case 'tablefield' + : call(function (source, lastKey, pushResult) + if lastKey then + return 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 + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + local uri = guide.getUri(source) + local parentNode = vm.compileNode(source.node) + for pn in parentNode:eachObject() do + searchFieldSwitch(pn.type, uri, pn, key, pushResult) end + end) + +function vm.compileByNodeChain(source, pushResult) + local lastKey + local src = source + while true do + local key, node = nodeSwitch(src.type, src, lastKey, pushResult) + if not key then + break + end + src = node + lastKey = key end end ---@param source vm.object +local function compileByParentNode(source) + if vm.getNode(source):isTyped() then + return + end + vm.compileByNodeChain(source, function (result) + vm.setNode(source, vm.compileNode(result)) + end) +end + +---@param source vm.node.object | vm.variable ---@return vm.node function vm.compileNode(source) if not source then @@ -1830,20 +2042,13 @@ function vm.compileNode(source) return cache end - if source.type == 'generic' then - vm.setNode(source, source) - local node = vm.getNode(source) - ---@cast node -? - return node - end - ---@cast source parser.object vm.setNode(source, vm.createNode(), true) - LOCK[source] = true - compileByGlobal(source) + vm.compileByGlobal(source) + vm.compileByVariable(source) compileByNode(source) + compileByParentNode(source) matchCall(source) - LOCK[source] = nil local node = vm.getNode(source) ---@cast node -? diff --git a/script/vm/def.lua b/script/vm/def.lua index f557f2214..5388a3849 100644 --- a/script/vm/def.lua +++ b/script/vm/def.lua @@ -19,109 +19,9 @@ simpleSwitch = util.switch() pushResult(loc) 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.global - ---@param key string - : call(function (suri, obj, key, pushResult) - if obj.cate == 'variable' then - local newGlobal = vm.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, false, pushResult) - end - end) - : case 'local' - : call(function (suri, obj, key, pushResult) - local sources = vm.getLocalSourcesSets(obj, key) - if sources then - for _, src in ipairs(sources) do - pushResult(src) - 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 nodeSwitch;nodeSwitch = util.switch() - : case 'field' - : case 'method' - : call(function (source, lastKey, pushResult) - return nodeSwitch(source.parent.type, source.parent, lastKey, pushResult) - end) - : case 'getfield' - : case 'setfield' - : case 'getmethod' - : case 'setmethod' - : case 'getindex' - : case 'setindex' - : call(function (source, lastKey, pushResult) - local parentNode = vm.compileNode(source.node) - local uri = guide.getUri(source) - local key = guide.getKeyName(source) - if type(key) ~= 'string' then - return - end - if lastKey then - key = key .. vm.ID_SPLITE .. lastKey - end - for pn in parentNode:eachObject() do - searchFieldSwitch(pn.type, uri, pn, key, pushResult) - end - return key, source.node - end) - : case 'tableindex' - : case 'tablefield' - : call(function (source, lastKey, pushResult) - if lastKey then - return - end - local key = guide.getKeyName(source) - if type(key) ~= 'string' then - return - end - local uri = guide.getUri(source) - local parentNode = vm.compileNode(source.node) - for pn in parentNode:eachObject() do - searchFieldSwitch(pn.type, uri, pn, key, pushResult) - end - end) - : case 'doc.see.field' - : call(function (source, lastKey, pushResult) - if lastKey then - return - end - 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 + : case 'doc.field' + : call(function (source, pushResult) + pushResult(source) end) ---@param source parser.object @@ -133,7 +33,7 @@ end ---@param source parser.object ---@param pushResult fun(src: parser.object) local function searchByLocalID(source, pushResult) - local idSources = vm.getLocalSourcesSets(source) + local idSources = vm.getVariableSets(source) if not idSources then return end @@ -142,25 +42,6 @@ local function searchByLocalID(source, pushResult) end end ----@param source parser.object ----@param pushResult fun(src: parser.object) -local function searchByParentNode(source, pushResult) - local lastKey - local src = source - while true do - local key, node = nodeSwitch(src.type, src, lastKey, pushResult) - if not key then - break - end - src = node - if lastKey then - lastKey = key .. vm.ID_SPLITE .. lastKey - else - lastKey = key - end - end -end - local function searchByNode(source, pushResult) local node = vm.compileNode(source) local suri = guide.getUri(source) @@ -188,10 +69,16 @@ function vm.getDefs(source) return end hasLocal = true + if source.type ~= 'local' + and source.type ~= 'getlocal' + and source.type ~= 'setlocal' + and source.type ~= 'doc.cast.name' then + return + end end if not mark[src] then mark[src] = true - if guide.isSet(src) + if guide.isAssign(src) or guide.isLiteral(src) then results[#results+1] = src end @@ -200,7 +87,7 @@ function vm.getDefs(source) searchBySimple(source, pushResult) searchByLocalID(source, pushResult) - searchByParentNode(source, pushResult) + vm.compileByNodeChain(source, pushResult) searchByNode(source, pushResult) return results diff --git a/script/vm/doc.lua b/script/vm/doc.lua index c3b924d22..6ac39910a 100644 --- a/script/vm/doc.lua +++ b/script/vm/doc.lua @@ -4,6 +4,13 @@ local guide = require 'parser.guide' local vm = require 'vm.vm' local config = require 'config' +---@class parser.object +---@field package _castTargetHead? parser.object | vm.global | false +---@field package _validVersions? table +---@field package _deprecated? parser.object | false +---@field package _async? boolean +---@field package _nodiscard? boolean + ---่Žทๅ–classไธŽalias ---@param suri uri ---@param name? string @@ -41,12 +48,38 @@ function vm.isMetaFile(uri) for _, doc in ipairs(status.ast.docs) do if doc.type == 'doc.meta' then cache.isMeta = true + cache.metaName = doc.name return true end end return false end +---@param uri uri +---@return string? +function vm.getMetaName(uri) + if not vm.isMetaFile(uri) then + return nil + end + local cache = files.getCache(uri) + if not cache then + return nil + end + if not cache.metaName then + return nil + end + return cache.metaName[1] +end + +---@param uri uri +---@return boolean +function vm.isMetaFileRequireable(uri) + if not vm.isMetaFile(uri) then + return false + end + return vm.getMetaName(uri) ~= '_' +end + ---@param doc parser.object ---@return table? function vm.getValidVersions(doc) @@ -112,6 +145,13 @@ local function getDeprecated(value) end end end + if value.type == 'function' then + local doc = getDeprecated(value.parent) + if doc then + value._deprecated = doc + return doc + end + end value._deprecated = false return nil end @@ -164,6 +204,9 @@ local function isAsync(value) value._async = false return false end + if value.type == 'main' then + return true + end return value.async == true end @@ -404,3 +447,42 @@ function vm.isDiagDisabledAt(uri, position, name, err) end return count > 0 end + +---@param doc parser.object +---@return (parser.object | vm.global)? +function vm.getCastTargetHead(doc) + if doc._castTargetHead ~= nil then + return doc._castTargetHead or nil + end + local name = doc.name[1]:match '^[^%.]+' + if not name then + doc._castTargetHead = false + return nil + end + local loc = guide.getLocal(doc, name, doc.start) + if loc then + doc._castTargetHead = loc + return loc + end + local global = vm.getGlobal('variable', name) + if global then + doc._castTargetHead = global + return global + end + return nil +end + +---@param doc parser.object +---@param key string +---@return boolean +function vm.docHasAttr(doc, key) + if not doc.docAttr then + return false + end + for _, name in ipairs(doc.docAttr.names) do + if name[1] == key then + return true + end + end + return false +end diff --git a/script/vm/field.lua b/script/vm/field.lua index b92c3a7bf..d41e886a5 100644 --- a/script/vm/field.lua +++ b/script/vm/field.lua @@ -16,7 +16,7 @@ local searchByNodeSwitch = util.switch() end) local function searchByLocalID(source, pushResult) - local fields = vm.getLocalFields(source, true) + local fields = vm.getVariableFields(source, true) if fields then for _, field in ipairs(fields) do pushResult(field) @@ -24,11 +24,19 @@ local function searchByLocalID(source, pushResult) end end -local function searchByNode(source, pushResult) +local function searchByNode(source, pushResult, mark) + mark = mark or {} + if mark[source] then + return + end + mark[source] = true local uri = guide.getUri(source) - vm.compileByParentNode(source, nil, true, function (field) + vm.compileByParentNode(source, vm.ANY, function (field) searchByNodeSwitch(field.type, uri, field, pushResult) end) + vm.compileByNodeChain(source, function (src) + searchByNode(src, pushResult, mark) + end) end ---@param source parser.object diff --git a/script/vm/function.lua b/script/vm/function.lua index f64b22624..c6df63498 100644 --- a/script/vm/function.lua +++ b/script/vm/function.lua @@ -1,5 +1,6 @@ ---@class vm -local vm = require 'vm.vm' +local vm = require 'vm.vm' +local guide = require 'parser.guide' ---@param arg parser.object ---@return parser.object? @@ -19,12 +20,15 @@ end ---@param func parser.object ---@return integer min ---@return number max +---@return integer def function vm.countParamsOfFunction(func) local min = 0 local max = 0 + local def = 0 if func.type == 'function' then if func.args then max = #func.args + def = max for i = #func.args, 1, -1 do local arg = func.args[i] if arg.type == '...' then @@ -44,6 +48,7 @@ function vm.countParamsOfFunction(func) if func.type == 'doc.type.function' then if func.args then max = #func.args + def = max for i = #func.args, 1, -1 do local arg = func.args[i] if arg.name and arg.name[1] =='...' then @@ -55,28 +60,80 @@ function vm.countParamsOfFunction(func) end end end - return min, max + return min, max, def +end + +---@param source parser.object +---@return integer min +---@return number max +---@return integer def +function vm.countParamsOfSource(source) + local min = 0 + local max = 0 + local def = 0 + local overloads = {} + if source.bindDocs then + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.overload' then + overloads[doc.overload] = true + end + end + end + local hasDocFunction + for nd in vm.compileNode(source):eachObject() do + if nd.type == 'doc.type.function' and not overloads[nd] then + hasDocFunction = true + ---@cast nd parser.object + local dmin, dmax, ddef = vm.countParamsOfFunction(nd) + if dmin > min then + min = dmin + end + if dmax > max then + max = dmax + end + if ddef > def then + def = ddef + end + end + end + if not hasDocFunction then + local dmin, dmax, ddef = vm.countParamsOfFunction(source) + if dmin > min then + min = dmin + end + if dmax > max then + max = dmax + end + if ddef > def then + def = ddef + end + end + return min, max, def end ---@param node vm.node ---@return integer min ---@return number max +---@return integer def function vm.countParamsOfNode(node) - local min, max + local min, max, def for n in node:eachObject() do if n.type == 'function' or n.type == 'doc.type.function' then ---@cast n parser.object - local fmin, fmax = vm.countParamsOfFunction(n) + local fmin, fmax, fdef = vm.countParamsOfFunction(n) if not min or fmin < min then min = fmin end if not max or fmax > max then max = fmax end + if not def or fdef > def then + def = fdef + end end end - return min or 0, max or math.huge + return min or 0, max or math.huge, def or 0 end ---@param func parser.object @@ -84,20 +141,17 @@ end ---@param mark? table ---@return integer min ---@return number max +---@return integer def function vm.countReturnsOfFunction(func, onlyDoc, mark) if func.type == 'function' then - ---@type integer? - local min - ---@type number? - local max + ---@type integer?, number?, integer? + local min, max, def local hasDocReturn if func.bindDocs then local lastReturn local n = 0 - ---@type integer? - local dmin - ---@type number? - local dmax + ---@type integer?, number?, integer? + local dmin, dmax, ddef for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.return' then hasDocReturn = true @@ -105,6 +159,7 @@ function vm.countReturnsOfFunction(func, onlyDoc, mark) n = n + 1 lastReturn = ret dmax = n + ddef = n if (not ret.name or ret.name[1] ~= '...') and not vm.compileNode(ret):isNullable() then dmin = n @@ -123,19 +178,25 @@ function vm.countReturnsOfFunction(func, onlyDoc, mark) if dmax and (not max or (dmax > max)) then max = dmax end + if ddef and (not def or (ddef > def)) then + def = ddef + end end if not onlyDoc and not hasDocReturn and func.returns then for _, ret in ipairs(func.returns) do - local rmin, rmax = vm.countList(ret, mark) - if not min or rmin < min then - min = rmin + local dmin, dmax, ddef = vm.countList(ret, mark) + if not min or dmin < min then + min = dmin end - if not max or rmax > max then - max = rmax + if not max or dmax > max then + max = dmax + end + if not def or ddef > def then + def = ddef end end end - return min or 0, max or math.huge + return min or 0, max or math.huge, def or 0 end if func.type == 'doc.type.function' then return vm.countList(func.returns) @@ -143,56 +204,129 @@ function vm.countReturnsOfFunction(func, onlyDoc, mark) error('not a function') end +---@param source parser.object +---@return integer min +---@return number max +---@return integer def +function vm.countReturnsOfSource(source) + local overloads = {} + local hasDocFunction + local min, max, def + if source.bindDocs then + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.overload' then + overloads[doc.overload] = true + local dmin, dmax, ddef = vm.countReturnsOfFunction(doc.overload) + if not min or dmin < min then + min = dmin + end + if not max or dmax > max then + max = dmax + end + if not def or ddef > def then + def = ddef + end + end + end + end + for nd in vm.compileNode(source):eachObject() do + if nd.type == 'doc.type.function' and not overloads[nd] then + ---@cast nd parser.object + hasDocFunction = true + local dmin, dmax, ddef = vm.countReturnsOfFunction(nd) + if not min or dmin < min then + min = dmin + end + if not max or dmax > max then + max = dmax + end + if not def or ddef > def then + def = ddef + end + end + end + if not hasDocFunction then + local dmin, dmax, ddef = vm.countReturnsOfFunction(source, true) + if not min or dmin < min then + min = dmin + end + if not max or dmax > max then + max = dmax + end + if not def or ddef > def then + def = ddef + end + end + return min, max, def +end + ---@param func parser.object ---@param mark? table ---@return integer min ---@return number max +---@return integer def function vm.countReturnsOfCall(func, args, mark) local funcs = vm.getMatchedFunctions(func, args, mark) - ---@type integer? - local min - ---@type number? - local max + ---@type integer?, number?, integer? + local min, max, def for _, f in ipairs(funcs) do - local rmin, rmax = vm.countReturnsOfFunction(f, false, mark) + local rmin, rmax, rdef = vm.countReturnsOfFunction(f, false, mark) if not min or rmin < min then min = rmin end if not max or rmax > max then max = rmax end + if not def or rdef > def then + def = rdef + end end - return min or 0, max or math.huge + return min or 0, max or math.huge, def or 0 end ---@param list parser.object[]? ---@param mark? table ---@return integer min ---@return number max +---@return integer def function vm.countList(list, mark) if not list then - return 0, 0 + return 0, 0, 0 end local lastArg = list[#list] if not lastArg then - return 0, 0 + return 0, 0, 0 end + ---@type integer, number, integer + local min, max, def = #list, #list, #list if lastArg.type == '...' - or lastArg.type == 'varargs' then - return #list - 1, math.huge - end - if lastArg.type == 'call' then + or lastArg.type == 'varargs' + or (lastArg.type == 'doc.type' and lastArg.name and lastArg.name[1] == '...') then + max = math.huge + elseif lastArg.type == 'call' then if not mark then mark = {} end if mark[lastArg] then - return #list - 1, math.huge + min = min - 1 + max = math.huge + else + mark[lastArg] = true + local rmin, rmax, rdef = vm.countReturnsOfCall(lastArg.node, lastArg.args, mark) + return min - 1 + rmin, max - 1 + rmax, def - 1 + rdef + end + end + for i = min, 1, -1 do + local arg = list[i] + if arg.type == 'doc.type' + and ((arg.name and arg.name[1] == '...') + or vm.compileNode(arg):isNullable()) then + min = i - 1 + else + break end - mark[lastArg] = true - local rmin, rmax = vm.countReturnsOfCall(lastArg.node, lastArg.args, mark) - return #list - 1 + rmin, #list - 1 + rmax end - return #list, #list + return min, max, def end ---@param func parser.object @@ -203,7 +337,7 @@ function vm.getMatchedFunctions(func, args, mark) local funcs = {} local node = vm.compileNode(func) for n in node:eachObject() do - if n.type == 'function' + if (n.type == 'function' and not vm.isVarargFunctionWithOverloads(n)) or n.type == 'doc.type.function' then funcs[#funcs+1] = n end @@ -228,3 +362,43 @@ function vm.getMatchedFunctions(func, args, mark) return matched end end + +---@param func table +---@return boolean +function vm.isVarargFunctionWithOverloads(func) + if func.type ~= 'function' then + return false + end + if not func.args then + return false + end + if func.args[1] and func.args[1].type == 'self' then + if not func.args[2] or func.args[2].type ~= '...' then + return false + end + else + if not func.args[1] or func.args[1].type ~= '...' then + return false + end + end + if not func.bindDocs then + return false + end + for _, doc in ipairs(func.bindDocs) do + if doc.type == 'doc.overload' then + return true + end + end + return false +end + +---@param func parser.object +---@return boolean +function vm.isEmptyFunction(func) + if #func > 0 then + return false + end + local startRow = guide.rowColOf(func.start) + local finishRow = guide.rowColOf(func.finish) + return finishRow - startRow <= 1 +end diff --git a/script/vm/generic.lua b/script/vm/generic.lua index 544e11c98..ed832d9bd 100644 --- a/script/vm/generic.lua +++ b/script/vm/generic.lua @@ -2,7 +2,8 @@ local vm = require 'vm.vm' ---@class parser.object ----@field _generic vm.generic +---@field package _generic vm.generic +---@field package _resolved vm.node ---@class vm.generic ---@field sign vm.sign @@ -29,16 +30,18 @@ local function cloneObject(source, resolved) } if resolved[key] then vm.setNode(newName, resolved[key], true) + newName._resolved = resolved[key] 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 = {}, + type = source.type, + start = source.start, + finish = source.finish, + parent = source.parent, + optional = source.optional, + types = {}, } for i, typeUnit in ipairs(source.types) do local newObj = cloneObject(typeUnit, resolved) @@ -121,11 +124,11 @@ function mt:resolve(uri, args) local protoNode = vm.compileNode(self.proto) local result = vm.createNode() for nd in protoNode:eachObject() do - if nd.type == 'global' then - ---@cast nd vm.global + if nd.type == 'global' or nd.type == 'variable' then + ---@cast nd vm.global | vm.variable result:merge(nd) else - ---@cast nd -vm.global + ---@cast nd -vm.global, -vm.variable local clonedObject = cloneObject(nd, resolved) if clonedObject then local clonedNode = vm.compileNode(clonedObject) @@ -136,6 +139,27 @@ function mt:resolve(uri, args) return result end +---@param source parser.object +---@return vm.node? +function vm.getGenericResolved(source) + if source.type ~= 'doc.generic.name' then + return nil + end + return source._resolved +end + +---@param source parser.object +---@param generic vm.generic +function vm.setGeneric(source, generic) + source._generic = generic +end + +---@param source parser.object +---@return vm.generic? +function vm.getGeneric(source) + return source._generic +end + ---@param proto vm.object ---@param sign vm.sign ---@return vm.generic diff --git a/script/vm/global.lua b/script/vm/global.lua index 22235681c..e830f6d8e 100644 --- a/script/vm/global.lua +++ b/script/vm/global.lua @@ -1,19 +1,27 @@ local util = require 'utility' local scope = require 'workspace.scope' local guide = require 'parser.guide' -local files = require 'files' -local ws = require 'workspace' +local config = require 'config' ---@class vm local vm = require 'vm.vm' +---@type table +local allGlobals = {} +---@type table> +local globalSubs = util.multiTable(2) + +---@class parser.object +---@field package _globalBase parser.object +---@field package _globalBaseMap table +---@field global vm.global + ---@class vm.global.link ----@field gets parser.object[] ----@field sets parser.object[] +---@field sets parser.object[] +---@field gets parser.object[] ---@class vm.global ---@field links table ---@field setsCache? table ----@field getsCache? table ---@field cate vm.global.cate local mt = {} mt.__index = mt @@ -24,9 +32,6 @@ mt.name = '' ---@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 @@ -35,14 +40,10 @@ end ---@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 ----@param suri uri +---@param suri uri ---@return parser.object[] function mt:getSets(suri) if not self.setsCache then @@ -53,6 +54,7 @@ function mt:getSets(suri) if self.setsCache[cacheUri] then return self.setsCache[cacheUri] end + local clock = os.clock() self.setsCache[cacheUri] = {} local cache = self.setsCache[cacheUri] for uri, link in pairs(self.links) do @@ -64,27 +66,28 @@ function mt:getSets(suri) end end end + local cost = os.clock() - clock + if cost > 0.1 then + log.warn('global-manager getSets costs', cost, self.name) + end return cache end ---@return parser.object[] -function mt:getGets(suri) - if not self.getsCache then - self.getsCache = {} +function mt:getAllSets() + if not self.setsCache then + self.setsCache = {} end - local scp = scope.getScope(suri) - local cacheUri = scp.uri or '' - if self.getsCache[cacheUri] then - return self.getsCache[cacheUri] + local cache = self.setsCache['*'] + if cache then + return cache 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 + cache = {} + self.setsCache['*'] = cache + for _, link in pairs(self.links) do + if link.sets then + for _, source in ipairs(link.sets) do + cache[#cache+1] = source end end end @@ -95,7 +98,6 @@ end function mt:dropUri(uri) self.links[uri] = nil self.setsCache = nil - self.getsCache = nil end ---@return string @@ -103,6 +105,11 @@ function mt:getName() return self.name end +---@return string +function mt:getCodeName() + return (self.name:gsub(vm.ID_SPLITE, '.')) +end + ---@return string function mt:asKeyName() return self.cate .. '|' .. self.name @@ -113,29 +120,57 @@ function mt:getKeyName() return self.name:match('[^' .. vm.ID_SPLITE .. ']+$') end +---@return string? +function mt:getFieldName() + return self.name:match(vm.ID_SPLITE .. '(.-)$') +end + ---@return boolean function mt:isAlive() return next(self.links) ~= nil end +---@param uri uri +---@return parser.object? +function mt:getParentBase(uri) + local parentID = self.name:match('^(.-)' .. vm.ID_SPLITE) + if not parentID then + return nil + end + local parentName = self.cate .. '|' .. parentID + local global = allGlobals[parentName] + if not global then + return nil + end + local link = global.links[uri] + if not link then + return nil + end + local luckyBoy = link.sets[1] or link.gets[1] + if not luckyBoy then + return nil + end + return vm.getGlobalBase(luckyBoy) +end + ---@param cate vm.global.cate ---@return vm.global local function createGlobal(name, cate) return setmetatable({ name = name, cate = cate, - links = util.multiTable(2), + links = util.multiTable(2, function () + return { + sets = {}, + gets = {}, + } + end), }, mt) end ---@class parser.object ----@field _globalNode vm.global|false ----@field _enums? (string|integer)[] - ----@type table -local allGlobals = {} ----@type table> -local globalSubs = util.multiTable(2) +---@field package _globalNode vm.global|false +---@field package _enums? parser.object[] local compileObject local compilerGlobalSwitch = util.switch() @@ -288,14 +323,15 @@ local compilerGlobalSwitch = util.switch() source._globalNode = class if source.signs then - source._sign = vm.createSign() - for _, sign in ipairs(source.signs) do - source._sign:addSign(vm.compileNode(sign)) + local sign = vm.createSign() + vm.setSign(source, sign) + for _, obj in ipairs(source.signs) do + sign:addSign(vm.compileNode(obj)) end if source.extends then for _, ext in ipairs(source.extends) do if ext.type == 'doc.type.table' then - ext._generic = vm.createGeneric(ext, source._sign) + vm.setGeneric(ext, vm.createGeneric(ext, sign)) end end end @@ -336,25 +372,37 @@ local compilerGlobalSwitch = util.switch() return end source._enums = {} - for _, field in ipairs(tbl) do - if field.type == 'tablefield' - or field.type == 'tableindex' then - if not field.value then - goto CONTINUE + if vm.docHasAttr(source, 'key') then + for _, field in ipairs(tbl) do + if field.type == 'tablefield' then + source._enums[#source._enums+1] = { + type = 'doc.type.string', + start = field.field.start, + finish = field.field.finish, + [1] = field.field[1], + } + elseif field.type == 'tableindex' then + source._enums[#source._enums+1] = { + type = 'doc.type.string', + start = field.index.start, + finish = field.index.finish, + [1] = field.index[1], + } end - local key = guide.getKeyName(field) - if not key then - goto CONTINUE - end - if field.value.type == 'integer' - or field.value.type == 'string' then - source._enums[#source._enums+1] = field.value[1] - end - if field.value.type == 'binary' - or field.value.type == 'unary' then - source._enums[#source._enums+1] = vm.getNumber(field.value) + end + else + for _, field in ipairs(tbl) do + if field.type == 'tablefield' then + source._enums[#source._enums+1] = field + local subType = vm.declareGlobal('type', name .. '.' .. field.field[1], uri) + subType:addSet(uri, field) + elseif field.type == 'tableindex' then + source._enums[#source._enums+1] = field + if field.index.type == 'string' then + local subType = vm.declareGlobal('type', name .. '.' .. field.index[1], uri) + subType:addSet(uri, field) + end end - ::CONTINUE:: end end end) @@ -365,6 +413,9 @@ local compilerGlobalSwitch = util.switch() if name == '_' then return end + if name == 'self' then + return + end local type = vm.declareGlobal('type', name, uri) type:addGet(uri, source) source._globalNode = type @@ -426,7 +477,7 @@ function vm.getGlobalFields(cate, name) end local cost = os.clock() - clock if cost > 0.1 then - log.warn('global-manager getFields cost %.3f', cost) + log.warn('global-manager getFields costs', cost) end return globals @@ -446,12 +497,17 @@ function vm.getGlobals(cate) end local cost = os.clock() - clock if cost > 0.1 then - log.warn('global-manager getGlobals cost %.3f', cost) + log.warn('global-manager getGlobals costs', cost) end return globals end +---@return table +function vm.getAllGlobals() + return allGlobals +end + ---@param suri uri ---@param cate vm.global.cate ---@return parser.object[] @@ -483,6 +539,33 @@ function vm.hasGlobalSets(suri, cate, name) return true end +---@param src parser.object +local function checkIsUndefinedGlobal(src) + local key = src[1] + + local uri = guide.getUri(src) + local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + local rspecial = config.get(uri, 'Lua.runtime.special') + + local node = src.node + return src.type == 'getglobal' and key and not ( + dglobals[key] or + rspecial[key] or + node.tag ~= '_ENV' or + vm.hasGlobalSets(uri, 'variable', key) + ) +end + +---@param src parser.object +---@return boolean +function vm.isUndefinedGlobal(src) + local node = vm.compileNode(src) + if node.undefinedGlobal == nil then + node.undefinedGlobal = checkIsUndefinedGlobal(src) + end + return node.undefinedGlobal +end + ---@param source parser.object function compileObject(source) if source._globalNode ~= nil then @@ -493,40 +576,82 @@ function compileObject(source) end ---@param source parser.object -local function compileSelf(source) - if source.parent.type ~= 'funcargs' then - return +---@return vm.global? +function vm.getGlobalNode(source) + return source._globalNode or nil +end + +---@param source parser.object +---@return parser.object[]? +function vm.getEnums(source) + return source._enums +end + +---@param source parser.object +---@return boolean +function vm.compileByGlobal(source) + local global = vm.getGlobalNode(source) + if not global then + return false end - ---@type parser.object - local node = source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node - if not node then - return + vm.setNode(source, global) + if global.cate == 'variable' then + if guide.isAssign(source) then + if vm.bindDocs(source) then + return true + end + if source.value and source.value.type ~= 'nil' then + vm.setNode(source, vm.compileNode(source.value)) + return true + end + else + if vm.bindAs(source) then + return true + end + local node = vm.traceNode(source) + if node then + vm.setNode(source, node, true) + return true + end + end end - local fields = vm.getLocalFields(source, false) - if not fields then - return + local globalBase = vm.getGlobalBase(source) + if not globalBase then + return false end - local nodeLocalID = vm.getLocalID(node) - local globalNode = node._globalNode - if not nodeLocalID and not globalNode then - return + local globalNode = vm.compileNode(globalBase) + vm.setNode(source, globalNode, true) + return true +end + +---@param source parser.object +---@return parser.object? +function vm.getGlobalBase(source) + if source._globalBase then + return source._globalBase end - for _, field in ipairs(fields) do - if field.type == 'setfield' then - local key = guide.getKeyName(field) - if key then - if nodeLocalID then - local myID = nodeLocalID .. vm.ID_SPLITE .. key - vm.insertLocalID(myID, field) - end - if globalNode then - local myID = globalNode:getName() .. vm.ID_SPLITE .. key - local myGlobal = vm.declareGlobal('variable', myID, guide.getUri(node)) - myGlobal:addSet(guide.getUri(node), field) - end - end - end + local global = vm.getGlobalNode(source) + if not global then + return nil end + ---@cast source parser.object + local root = guide.getRoot(source) + if not root._globalBaseMap then + root._globalBaseMap = {} + end + local name = global:asKeyName() + if not root._globalBaseMap[name] then + ---@diagnostic disable-next-line: missing-fields + root._globalBaseMap[name] = { + type = 'globalbase', + parent = root, + global = global, + start = 0, + finish = 0, + } + end + source._globalBase = root._globalBaseMap[name] + return source._globalBase end ---@param source parser.object @@ -551,18 +676,6 @@ local function compileAst(source) }, function (src) compileObject(src) end) - - --[[ - local mt - function mt:xxx() - self.a = 1 - end - - mt.a --> find this definition - ]] - guide.eachSourceType(source, 'self', function (src) - compileSelf(src) - end) end ---@param uri uri @@ -580,24 +693,7 @@ local function dropUri(uri) end end -for uri in files.eachFile() do - local state = files.getState(uri) - if state then - compileAst(state.ast) - end -end - ----@async -files.watch(function (ev, uri) - if ev == 'update' then - dropUri(uri) - ws.awaitReady(uri) - local state = files.getState(uri) - if state then - compileAst(state.ast) - end - end - if ev == 'remove' then - dropUri(uri) - end -end) +return { + compileAst = compileAst, + dropUri = dropUri, +} diff --git a/script/vm/infer.lua b/script/vm/infer.lua index 263b2500b..3f3d0e3a8 100644 --- a/script/vm/infer.lua +++ b/script/vm/infer.lua @@ -5,10 +5,13 @@ local guide = require 'parser.guide' local vm = require 'vm.vm' ---@class vm.infer +---@field node vm.node ---@field views table ----@field cachedView? string ----@field node? vm.node ---@field _drop table +---@field _lastView? string +---@field _lastViewUri? uri +---@field _lastViewDefault? any +---@field _subViews? string[] local mt = {} mt.__index = mt mt._hasTable = false @@ -34,7 +37,7 @@ local inferSorted = { ['nil'] = 100, } -local viewNodeSwitch = util.switch() +local viewNodeSwitch;viewNodeSwitch = util.switch() : case 'nil' : case 'boolean' : case 'string' @@ -60,7 +63,7 @@ local viewNodeSwitch = util.switch() : case 'function' : call(function (source, infer) local parent = source.parent - if guide.isSet(parent) then + if guide.isAssign(parent) then infer._hasFunctionDef = true end return source.type @@ -82,6 +85,14 @@ local viewNodeSwitch = util.switch() return source.name end end) + : case 'doc.type' + : call(function (source, infer, uri) + local buf = {} + for _, tp in ipairs(source.types) do + buf[#buf+1] = viewNodeSwitch(tp.type, tp, infer, uri) + end + return table.concat(buf, '|') + end) : case 'doc.type.name' : call(function (source, infer, uri) if source.signs then @@ -99,8 +110,16 @@ local viewNodeSwitch = util.switch() return vm.getInfer(source.proto):view(uri) end) : case 'doc.generic.name' - : call(function (source, infer) - return ('<%s>'):format(source[1]) + : call(function (source, infer, uri) + local resolved = vm.getGenericResolved(source) + if resolved then + return vm.getInfer(resolved):view(uri) + end + if source.generic and source.generic.extends then + return ('<%s:%s>'):format(source[1], vm.getInfer(source.generic.extends):view(uri)) + else + return ('<%s>'):format(source[1]) + end end) : case 'doc.type.array' : call(function (source, infer, uri) @@ -118,11 +137,13 @@ local viewNodeSwitch = util.switch() for i, sign in ipairs(source.signs) do buf[i] = vm.getInfer(sign):view(uri) end - if infer._drop then - local node = vm.compileNode(source) - for c in node:eachObject() do - if guide.isLiteral(c) then - infer._drop[c] = true + local node = vm.compileNode(source) + for c in node:eachObject() do + if guide.isLiteral(c) then + ---@cast c parser.object + local view = vm.getInfer(c):view(uri) + if view then + infer._drop[view] = true end end end @@ -134,28 +155,26 @@ local viewNodeSwitch = util.switch() infer._hasTable = true return end - if infer._drop and infer._drop[source] then - infer._hasTable = true - return - end infer._hasClass = true local buf = {} - buf[#buf+1] = '{ ' + buf[#buf+1] = source.isTuple and '[' or '{ ' for i, field in ipairs(source.fields) do if i > 1 then buf[#buf+1] = ', ' end - local key = field.name - if key.type == 'doc.type' then - buf[#buf+1] = ('[%s]: '):format(vm.getInfer(key):view(uri)) - elseif type(key[1]) == 'string' then - buf[#buf+1] = key[1] .. ': ' - else - buf[#buf+1] = ('[%q]: '):format(key[1]) + if not source.isTuple then + local key = field.name + if key.type == 'doc.type' then + buf[#buf+1] = ('[%s]: '):format(vm.getInfer(key):view(uri)) + elseif type(key[1]) == 'string' then + buf[#buf+1] = key[1] .. ': ' + else + buf[#buf+1] = ('[%q]: '):format(key[1]) + end end buf[#buf+1] = vm.getInfer(field.extends):view(uri) end - buf[#buf+1] = ' }' + buf[#buf+1] = source.isTuple and ']' or ' }' return table.concat(buf) end) : case 'doc.type.string' @@ -218,11 +237,25 @@ local viewNodeSwitch = util.switch() end return ('fun(%s)%s'):format(argView, regView) end) + : case 'doc.field.name' + : call(function (source, infer, uri) + return vm.viewKey(source, uri) + end) ---@class vm.node ---@field lastInfer? vm.infer ----@param source vm.object | vm.node +---@param node? vm.node +---@return vm.infer +local function createInfer(node) + local infer = setmetatable({ + node = node, + _drop = {}, + }, mt) + return infer +end + +---@param source vm.node.object | vm.node ---@return vm.infer function vm.getInfer(source) ---@type vm.node @@ -237,10 +270,7 @@ function vm.getInfer(source) if node.lastInfer then return node.lastInfer end - local infer = setmetatable({ - node = node, - _drop = {}, - }, mt) + local infer = createInfer(node) node.lastInfer = infer return infer @@ -271,9 +301,14 @@ function mt:_trim() end ---@param uri uri ----@return table function mt:_eraseAlias(uri) - local drop = {} + local count = 0 + for _ in pairs(self.views) do + count = count + 1 + end + if count <= 1 then + return + end local expandAlias = config.get(uri, 'Lua.hover.expandAlias') for n in self.node:eachObject() do if n.type == 'global' and n.cate == 'type' then @@ -284,8 +319,8 @@ function mt:_eraseAlias(uri) for _, set in ipairs(n:getSets(uri)) do if set.type == 'doc.alias' then if expandAlias then - drop[n.name] = true - local newInfer = {} + self._drop[n.name] = true + local newInfer = createInfer() for _, ext in ipairs(set.extends.types) do viewNodeSwitch(ext.type, ext, newInfer, uri) end @@ -294,19 +329,9 @@ function mt:_eraseAlias(uri) end else for _, ext in ipairs(set.extends.types) do - local view = viewNodeSwitch(ext.type, ext, {}, uri) + local view = viewNodeSwitch(ext.type, ext, createInfer(), uri) if view and view ~= n.name then - drop[view] = true - end - end - end - end - if set.type == 'doc.class' then - if set.extends then - for _, ext in ipairs(set.extends) do - if ext.type == 'doc.extends.name' then - local view = ext[1] - drop[view] = true + self._drop[view] = true end end end @@ -316,7 +341,6 @@ function mt:_eraseAlias(uri) ::CONTINUE:: end end - return drop end ---@param uri uri @@ -364,9 +388,11 @@ function mt:_computeViews(uri) self.views = {} for n in self.node:eachObject() do - local view = viewNodeSwitch(n.type, n, self, uri) - if view then - self.views[view] = true + if not n.hideView then + local view = viewNodeSwitch(n.type, n, self, uri) + if view then + self.views[view] = true + end end end @@ -377,20 +403,29 @@ end ---@param default? string ---@return string function mt:view(uri, default) + if self._lastView + and self._lastViewUri == uri + and self._lastViewDefault == default then + return self._lastView + end + self._lastViewUri = uri + self._lastViewDefault = default + self:_computeViews(uri) if self.views['any'] then + self._lastView = 'any' return 'any' end - local drop if self._hasClass then - drop = self:_eraseAlias(uri) + self:_eraseAlias(uri) end local array = {} + self._subViews = array for view in pairs(self.views) do - if not drop or not drop[view] then + if not self._drop[view] then array[#array+1] = view end end @@ -422,13 +457,23 @@ function mt:view(uri, default) end if self.node:isOptional() then - if max > 1 then - view = '(' .. view .. ')?' + if #array == 0 then + view = 'nil' else - view = view .. '?' + if max > 1 + or view:find(guide.notNamePattern .. guide.namePattern .. '$') then + view = '(' .. view .. ')?' + else + view = view .. '?' + end end end + if #view > 200 then + view = view:sub(1, 180) .. '...(too long)...' .. view:sub(-10) + end + + self._lastView = view return view end @@ -438,21 +483,11 @@ function mt:eachView(uri) return next, self.views end ----@param other vm.infer ----@return vm.infer -function mt:merge(other) - if self == vm.NULL then - return other - end - if other == vm.NULL then - return self - end - - local infer = setmetatable({ - node = vm.createNode(self.node, other.node), - }, mt) - - return infer +---@param uri uri +---@return string[] +function mt:getSubViews(uri) + self:view(uri) + return self._subViews end ---@return string? @@ -467,7 +502,12 @@ function mt:viewLiterals() or n.type == 'number' or n.type == 'integer' or n.type == 'boolean' then - local literal = util.viewLiteral(n[1]) + local literal + if n.type == 'string' then + literal = util.viewString(n[1], n[2]) + else + literal = util.viewLiteral(n[1]) + end if literal and not mark[literal] then literals[#literals+1] = literal mark[literal] = true @@ -515,5 +555,56 @@ end ---@param uri uri ---@return string? function vm.viewObject(source, uri) - return viewNodeSwitch(source.type, source, {}, uri) + local infer = createInfer() + return viewNodeSwitch(source.type, source, infer, uri) +end + +---@param source parser.object +---@param uri uri +---@return string? +---@return string|number|boolean|nil +function vm.viewKey(source, uri) + if source.type == 'doc.type' then + if #source.types == 1 then + return vm.viewKey(source.types[1], uri) + else + local key = vm.getInfer(source):view(uri) + return '[' .. key .. ']', key + end + end + if source.type == 'tableindex' + or source.type == 'setindex' + or source.type == 'getindex' then + local index = source.index + local name = vm.getInfer(index):viewLiterals() + if not name then + return nil + end + return ('[%s]'):format(name), name + end + if source.type == 'tableexp' then + return ('[%d]'):format(source.tindex), source.tindex + end + if source.type == 'doc.field' then + return vm.viewKey(source.field, uri) + end + if source.type == 'doc.type.field' then + return vm.viewKey(source.name, uri) + end + if source.type == 'doc.type.name' then + return '[' .. source[1] .. ']', source[1] + end + if source.type == 'doc.type.string' then + local name = util.viewString(source[1], source[2]) + return ('[%s]'):format(name), name + end + local key = vm.getKeyName(source) + if key == nil then + return nil + end + if type(key) == 'string' then + return key, key + else + return ('[%s]'):format(key), key + end end diff --git a/script/vm/init.lua b/script/vm/init.lua index 87c046f01..f1d3292a3 100644 --- a/script/vm/init.lua +++ b/script/vm/init.lua @@ -11,12 +11,15 @@ require 'vm.field' require 'vm.doc' require 'vm.type' require 'vm.library' -require 'vm.runner' +require 'vm.tracer' require 'vm.infer' require 'vm.generic' require 'vm.sign' -require 'vm.local-id' +require 'vm.variable' require 'vm.global' require 'vm.function' require 'vm.operator' +require 'vm.visible' +require 'vm.precompile' + return vm diff --git a/script/vm/local-id.lua b/script/vm/local-id.lua deleted file mode 100644 index 9168d6801..000000000 --- a/script/vm/local-id.lua +++ /dev/null @@ -1,242 +0,0 @@ -local util = require 'utility' -local guide = require 'parser.guide' ----@class vm -local vm = require 'vm.vm' - ----@class parser.object ----@field _localID string|false ----@field _localIDs table - -local compileLocalID, getLocal - -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 - compileLocalID(ref) - end - end) - : case 'setlocal' - : case 'getlocal' - : call(function (source) - source._localID = ('%d'):format(source.node.start) - 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 .. vm.ID_SPLITE .. key - source.field._localID = source._localID - if source.type == 'getfield' then - 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 .. vm.ID_SPLITE .. key - source.method._localID = source._localID - if source.type == 'getmethod' then - 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 .. vm.ID_SPLITE .. key - source.index._localID = source._localID - if source.type == 'setindex' then - compileLocalID(source.next) - end - end) - -local leftSwitch = util.switch() - : case 'field' - : case 'method' - : call(function (source) - return getLocal(source.parent) - end) - : case 'getfield' - : case 'setfield' - : case 'getmethod' - : case 'setmethod' - : case 'getindex' - : case 'setindex' - : call(function (source) - return getLocal(source.node) - end) - : case 'getlocal' - : call(function (source) - return source.node - end) - : case 'local' - : case 'self' - : call(function (source) - return source - end) - ----@param source parser.object ----@return parser.object? -function getLocal(source) - return leftSwitch(source.type, source) -end - ----@param id string ----@param source parser.object -function vm.insertLocalID(id, source) - local root = guide.getRoot(source) - if not root._localIDs then - root._localIDs = util.multiTable(2, function () - return { - sets = {}, - gets = {}, - } - end) - end - local sources = root._localIDs[id] - if guide.isSet(source) then - sources.sets[#sources.sets+1] = source - else - sources.gets[#sources.gets+1] = source - end -end - -function compileLocalID(source) - if not source then - return - end - source._localID = false - if not compileSwitch:has(source.type) then - return - end - compileSwitch(source.type, source) - local id = source._localID - if not id then - return - end - vm.insertLocalID(id, source) -end - ----@param source parser.object ----@return string|false -function vm.getLocalID(source) - if source._localID ~= nil then - return source._localID - end - source._localID = false - local loc = getLocal(source) - if not loc then - return source._localID - end - compileLocalID(loc) - return source._localID -end - ----@param source parser.object ----@param key? string ----@return parser.object[]? -function vm.getLocalSourcesSets(source, key) - local id = vm.getLocalID(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 .. vm.ID_SPLITE .. key - end - return root._localIDs[id].sets -end - ----@param source parser.object ----@param key? string ----@return parser.object[]? -function vm.getLocalSourcesGets(source, key) - local id = vm.getLocalID(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 .. vm.ID_SPLITE .. key - end - return root._localIDs[id].gets -end - ----@param source parser.object ----@param includeGets boolean ----@return parser.object[]? -function vm.getLocalFields(source, includeGets) - local id = vm.getLocalID(source) - if not id then - return nil - end - local root = guide.getRoot(source) - if not root._localIDs then - return nil - end - -- TODO๏ผšoptimize - local clock = os.clock() - 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) == vm.ID_SPLITE - -- only one field - and not lid:find(vm.ID_SPLITE, #id + 2) then - for _, src in ipairs(sources.sets) do - fields[#fields+1] = src - end - if includeGets then - for _, src in ipairs(sources.gets) do - fields[#fields+1] = src - end - end - end - end - local cost = os.clock() - clock - if cost > 1.0 then - log.warn('local-id getFields takes %.3f seconds', cost) - end - return fields -end diff --git a/script/vm/node.lua b/script/vm/node.lua index 49207b137..bc1dfcb10 100644 --- a/script/vm/node.lua +++ b/script/vm/node.lua @@ -3,21 +3,27 @@ local files = require 'files' local vm = require 'vm.vm' local ws = require 'workspace.workspace' local guide = require 'parser.guide' +local timer = require 'timer' +local util = require 'utility' ---@type table -vm.nodeCache = {} +vm.nodeCache = setmetatable({}, util.MODE_K) ----@alias vm.node.object vm.object | vm.global +---@alias vm.node.object vm.object | vm.global | vm.variable ---@class vm.node ---@field [integer] vm.node.object ---@field [vm.node.object] true +---@field fields? table +---@field undefinedGlobal boolean? local mt = {} mt.__index = mt mt.id = 0 mt.type = 'vm.node' mt.optional = nil mt.data = nil +mt.hasDefined = nil +mt.originNode = nil ---@param node vm.node | vm.node.object ---@return vm.node @@ -53,6 +59,19 @@ function mt:isEmpty() return #self == 0 end +---@return boolean +function mt:isTyped() + for _, c in ipairs(self) do + if c.type == 'global' and c.cate == 'type' then + return true + end + if guide.isLiteral(c) then + return true + end + end + return false +end + function mt:clear() self.optional = nil for i, c in ipairs(self) do @@ -67,21 +86,6 @@ function mt:get(n) return self[n] end -function mt:setData(k, v) - if not self.data then - self.data = {} - end - self.data[k] = v -end - ----@return any -function mt:getData(k) - if not self.data then - return nil - end - return self.data[k] -end - function mt:addOptional() self.optional = true end @@ -245,7 +249,8 @@ function mt:remove(name) or (c.type == 'doc.type.table' and name == 'table') or (c.type == 'doc.type.array' and name == 'table') or (c.type == 'doc.type.sign' and name == c.node[1]) - or (c.type == 'doc.type.function' and name == 'function') then + or (c.type == 'doc.type.function' and name == 'function') + or (c.type == 'doc.type.string' and name == 'string') then table.remove(self, index) self[c] = nil end @@ -253,9 +258,10 @@ function mt:remove(name) return self end +---@param uri uri ---@param name string -function mt:narrow(name) - if name ~= 'nil' and self.optional == true then +function mt:narrow(uri, name) + if self.optional == true then self.optional = nil end for index = #self, 1, -1 do @@ -266,12 +272,13 @@ function mt:narrow(name) or (c.type == 'doc.type.table' and name == 'table') or (c.type == 'doc.type.array' and name == 'table') or (c.type == 'doc.type.sign' and name == c.node[1]) - or (c.type == 'doc.type.function' and name == 'function') then + or (c.type == 'doc.type.function' and name == 'function') + or (c.type == 'doc.type.string' and name == 'string') then goto CONTINUE end if c.type == 'global' and c.cate == 'type' then if (c.name == name) - or (c.name == 'integer' and name == 'number') then + or (vm.isSubType(uri, c.name, name)) then goto CONTINUE end end @@ -285,7 +292,7 @@ function mt:narrow(name) return self end ----@param obj vm.object +---@param obj vm.object | vm.variable function mt:removeObject(obj) for index, c in ipairs(self) do if c == obj then @@ -391,7 +398,7 @@ function mt:copy() return vm.createNode(self) end ----@param source vm.object +---@param source vm.node.object | vm.generic ---@param node vm.node | vm.node.object ---@param cover? boolean ---@return vm.node @@ -422,7 +429,7 @@ function vm.setNode(source, node, cover) return me end ----@param source vm.object +---@param source vm.node.object ---@return vm.node? function vm.getNode(source) return vm.nodeCache[source] @@ -475,10 +482,22 @@ function vm.createNode(a, b) return node end +---@type timer? +local delayTimer files.watch(function (ev, uri) if ev == 'version' then if ws.isReady(uri) then - vm.clearNodeCache() + if CACHEALIVE then + if delayTimer then + delayTimer:restart() + end + delayTimer = timer.wait(1, function () + delayTimer = nil + vm.clearNodeCache() + end) + else + vm.clearNodeCache() + end end end end) diff --git a/script/vm/operator.lua b/script/vm/operator.lua index 9dea01c1f..7ce2b30d0 100644 --- a/script/vm/operator.lua +++ b/script/vm/operator.lua @@ -73,14 +73,22 @@ local function checkOperators(operators, op, value, result) local valueNode = vm.compileNode(value) local expNode = vm.compileNode(operator.exp) local uri = guide.getUri(operator) - if not vm.isSubType(uri, valueNode, expNode) then - goto CONTINUE + for vo in valueNode:eachObject() do + if vm.isSubType(uri, vo, expNode) then + if not result then + result = vm.createNode() + end + result:merge(vm.compileNode(operator.extends)) + return result + end end + else + if not result then + result = vm.createNode() + end + result:merge(vm.compileNode(operator.extends)) + return result end - if not result then - result = vm.createNode() - end - result:merge(vm.compileNode(operator.extends)) ::CONTINUE:: end return result @@ -118,6 +126,7 @@ vm.unarySwich = util.switch() if result == nil then vm.setNode(source, vm.declareGlobal('type', 'boolean')) else + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'boolean', start = source.start, @@ -147,6 +156,7 @@ vm.unarySwich = util.switch() vm.setNode(source, node or vm.declareGlobal('type', 'number')) end else + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'number', start = source.start, @@ -163,6 +173,7 @@ vm.unarySwich = util.switch() local node = vm.runOperator('bnot', source[1]) vm.setNode(source, node or vm.declareGlobal('type', 'integer')) else + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'integer', start = source.start, @@ -198,7 +209,10 @@ vm.binarySwitch = util.switch() elseif r1 == false then vm.setNode(source, node2) else - local node = node1:copy():setTruthy():merge(node2) + local node = node1:copy():setTruthy() + if not source[2].hasExit then + node:merge(node2) + end vm.setNode(source, node) end end) @@ -212,6 +226,7 @@ vm.binarySwitch = util.switch() if source.op.type == '~=' then result = not result end + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'boolean', start = source.start, @@ -236,6 +251,7 @@ vm.binarySwitch = util.switch() or op == '&' and a & b or op == '|' and a | b or op == '~' and a ~ b + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'integer', start = source.start, @@ -245,7 +261,9 @@ vm.binarySwitch = util.switch() }) else local node = vm.runOperator(binaryMap[op], source[1], source[2]) - vm.setNode(source, node or vm.declareGlobal('type', 'integer')) + if node then + vm.setNode(source, node) + end end end) : case '+' @@ -272,8 +290,9 @@ vm.binarySwitch = util.switch() or op == '%' and a % b or op == '//' and a // b or op == '^' and a ^ b + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { - type = math.type(result) == 'integer' and 'integer' or 'number', + type = (op == '//' or math.type(result) == 'integer') and 'integer' or 'number', start = source.start, finish = source.finish, parent = source, @@ -288,21 +307,42 @@ vm.binarySwitch = util.switch() if op == '+' or op == '-' or op == '*' - or op == '//' or op == '%' then local uri = guide.getUri(source) local infer1 = vm.getInfer(source[1]) local infer2 = vm.getInfer(source[2]) - if infer1:hasType(uri, 'integer') - or infer2:hasType(uri, 'integer') then - if not infer1:hasType(uri, 'number') - and not infer2:hasType(uri, 'number') then - vm.setNode(source, vm.declareGlobal('type', 'integer')) - return - end + if infer1:hasType(uri, 'integer') + and infer2:hasType(uri, 'integer') then + vm.setNode(source, vm.declareGlobal('type', 'integer')) + return + end + if (infer1:hasType(uri, 'number') or infer1:hasType(uri, 'integer')) + and (infer2:hasType(uri, 'number') or infer2:hasType(uri, 'integer')) then + vm.setNode(source, vm.declareGlobal('type', 'number')) + return + end + end + if op == '/' + or op == '^' then + local uri = guide.getUri(source) + local infer1 = vm.getInfer(source[1]) + local infer2 = vm.getInfer(source[2]) + if (infer1:hasType(uri, 'integer') or infer1:hasType(uri, 'number')) + and (infer2:hasType(uri, 'integer') or infer2:hasType(uri, 'number')) then + vm.setNode(source, vm.declareGlobal('type', 'number')) + return + end + end + if op == '//' then + local uri = guide.getUri(source) + local infer1 = vm.getInfer(source[1]) + local infer2 = vm.getInfer(source[2]) + if (infer1:hasType(uri, 'integer') or infer1:hasType(uri, 'number')) + and (infer2:hasType(uri, 'integer') or infer2:hasType(uri, 'number')) then + vm.setNode(source, vm.declareGlobal('type', 'integer')) + return end end - vm.setNode(source, node or vm.declareGlobal('type', 'number')) end end) : case '..' @@ -330,6 +370,7 @@ vm.binarySwitch = util.switch() end end end + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'string', start = source.start, @@ -338,8 +379,26 @@ vm.binarySwitch = util.switch() [1] = a .. b, }) else + local uri = guide.getUri(source) + local infer1 = vm.getInfer(source[1]) + local infer2 = vm.getInfer(source[2]) + if ( + infer1:hasType(uri, 'integer') + or infer1:hasType(uri, 'number') + or infer1:hasType(uri, 'string') + ) + and ( + infer2:hasType(uri, 'integer') + or infer2:hasType(uri, 'number') + or infer2:hasType(uri, 'string') + ) then + vm.setNode(source, vm.declareGlobal('type', 'string')) + return + end local node = vm.runOperator(binaryMap[source.op.type], source[1], source[2]) - vm.setNode(source, node or vm.declareGlobal('type', 'string')) + if node then + vm.setNode(source, node) + end end end) : case '>' @@ -355,6 +414,7 @@ vm.binarySwitch = util.switch() or op == '<' and a < b or op == '>=' and a >= b or op == '<=' and a <= b + ---@diagnostic disable-next-line: missing-fields vm.setNode(source, { type = 'boolean', start = source.start, diff --git a/script/vm/precompile.lua b/script/vm/precompile.lua new file mode 100644 index 000000000..47c630260 --- /dev/null +++ b/script/vm/precompile.lua @@ -0,0 +1,20 @@ +local files = require 'files' +local global = require 'vm.global' +local variable = require 'vm.variable' + +---@async +files.watch(function (ev, uri) + if ev == 'update' then + global.dropUri(uri) + end + if ev == 'remove' then + global.dropUri(uri) + end + if ev == 'compile' then + local state = files.getLastState(uri) + if state then + global.compileAst(state.ast) + variable.compileAst(state.ast) + end + end +end) diff --git a/script/vm/ref.lua b/script/vm/ref.lua index 0135d11f5..89b9d3c0c 100644 --- a/script/vm/ref.lua +++ b/script/vm/ref.lua @@ -29,7 +29,10 @@ simpleSwitch = util.switch() ---@async local function searchInAllFiles(suri, searcher, notify) + await.delay() + searcher(suri) + await.delay() local uris = {} for uri in files.eachFile(suri) do @@ -62,8 +65,13 @@ local function searchInAllFiles(suri, searcher, notify) end ---@async -local function searchField(source, pushResult, defMap, fileNotify) +local function searchWord(source, pushResult, defMap, fileNotify) local key = guide.getKeyName(source) + if not key then + return + end + + local global = vm.getGlobalNode(source) ---@param src parser.object local function checkDef(src) @@ -75,21 +83,31 @@ local function searchField(source, pushResult, defMap, fileNotify) 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 + if not text:find(key, 1, true) then return end local state = files.getState(uri) if not state then return end + + if global then + local globalName = global:asKeyName() + ---@async + guide.eachSourceTypes(state.ast, {'getglobal', 'setglobal', 'setfield', 'getfield', 'setmethod', 'getmethod', 'setindex', 'getindex', 'doc.type.name', 'doc.class.name', 'doc.alias.name', 'doc.extends.name'}, function (src) + local myGlobal = vm.getGlobalNode(src) + if myGlobal and myGlobal:asKeyName() == globalName then + pushResult(src) + await.delay() + end + end) + end ---@async guide.eachSourceTypes(state.ast, {'getfield', 'setfield'}, function (src) if src.field and src.field[1] == key then @@ -165,13 +183,36 @@ local nodeSwitch = util.switch() return end - searchField(source, pushResult, defMap, fileNotify) + searchWord(source, pushResult, defMap, fileNotify) end) : case 'tablefield' : case 'tableindex' + : case 'doc.field.name' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchWord(source, pushResult, defMap, fileNotify) + end) + : case 'setglobal' + : case 'getglobal' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchWord(source, pushResult, defMap, fileNotify) + end) + : case 'doc.alias.name' + : case 'doc.class.name' + : case 'doc.enum.name' + ---@async + : call(function (source, pushResult, defMap, fileNotify) + searchWord(source.parent, pushResult, defMap, fileNotify) + end) + : case 'doc.alias' + : case 'doc.class' + : case 'doc.enum' + : case 'doc.type.name' + : case 'doc.extends.name' ---@async : call(function (source, pushResult, defMap, fileNotify) - searchField(source, pushResult, defMap, fileNotify) + searchWord(source, pushResult, defMap, fileNotify) end) : case 'function' : case 'doc.type.function' @@ -189,13 +230,13 @@ end ---@param source parser.object ---@param pushResult fun(src: parser.object) local function searchByLocalID(source, pushResult) - local sourceSets = vm.getLocalSourcesSets(source) + local sourceSets = vm.getVariableSets(source) if sourceSets then for _, src in ipairs(sourceSets) do pushResult(src) end end - local sourceGets = vm.getLocalSourcesGets(source) + local sourceGets = vm.getVariableGets(source) if sourceGets then for _, src in ipairs(sourceGets) do pushResult(src) @@ -211,18 +252,20 @@ function searchByParentNode(source, pushResult, defMap, fileNotify) nodeSwitch(source.type, source, pushResult, defMap, fileNotify) end -local function searchByNode(source, pushResult) - local node = vm.compileNode(source) +local function searchByGlobal(source, pushResult) + if source.type == 'field' + or source.type == 'method' + or source.type == 'doc.class.name' + or source.type == 'doc.alias.name' then + source = source.parent + end + local node = vm.getGlobalNode(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 + for _, set in ipairs(node:getSets(uri)) do + pushResult(set) end end @@ -237,16 +280,17 @@ local function searchByDef(source, pushResult) or source.type == 'method' then source = source.parent end + if source.type == 'doc.field.name' then + source = source.parent + end defMap[source] = true - if guide.isSet(source) then - local defs = vm.getDefs(source) - for _, def in ipairs(defs) do - pushResult(def) - end - else - local defs = vm.getDefs(source) - for _, def in ipairs(defs) do - pushResult(def) + local defs = vm.getDefs(source) + for _, def in ipairs(defs) do + pushResult(def) + if not guide.isLiteral(def) + and def.type ~= 'doc.alias' + and def.type ~= 'doc.class' + and def.type ~= 'doc.enum' then defMap[def] = true end end @@ -276,7 +320,7 @@ function vm.getRefs(source, fileNotify) searchBySimple(source, pushResult) searchByLocalID(source, pushResult) - searchByNode(source, pushResult) + searchByGlobal(source, pushResult) local defMap = searchByDef(source, pushResult) searchByParentNode(source, pushResult, defMap, fileNotify) diff --git a/script/vm/runner.lua b/script/vm/runner.lua deleted file mode 100644 index 2f0479834..000000000 --- a/script/vm/runner.lua +++ /dev/null @@ -1,361 +0,0 @@ ----@class vm -local vm = require 'vm.vm' -local guide = require 'parser.guide' - ----@alias vm.runner.callback fun(src: parser.object, node?: vm.node) - ----@class vm.runner ----@field _loc parser.object ----@field _casts parser.object[] ----@field _callback vm.runner.callback ----@field _mark table ----@field _has table ----@field _main parser.object -local mt = {} -mt.__index = mt -mt._index = 1 - ----@return parser.object[] -function mt:_getCasts() - local root = guide.getRoot(self._loc) - if not root._casts then - root._casts = {} - local docs = root.docs - for _, doc in ipairs(docs) do - if doc.type == 'doc.cast' and doc.loc then - root._casts[#root._casts+1] = doc - end - end - end - return root._casts -end - ----@param obj parser.object -function mt:_markHas(obj) - while true do - if self._has[obj] then - return - end - self._has[obj] = true - if obj == self._main then - return - end - obj = obj.parent - end -end - -function mt:_collect() - local startPos = self._loc.start - local finishPos = 0 - - for _, ref in ipairs(self._loc.ref) do - if ref.type == 'getlocal' - or ref.type == 'setlocal' then - self:_markHas(ref) - if ref.finish > finishPos then - finishPos = ref.finish - end - end - end - - local casts = self:_getCasts() - for _, cast in ipairs(casts) do - if cast.loc[1] == self._loc[1] - and cast.start > startPos - and cast.finish < finishPos - and guide.getLocal(self._loc, self._loc[1], cast.start) == self._loc then - self._casts[#self._casts+1] = cast - end - end -end - ----@param pos integer ----@param topNode vm.node ----@return vm.node -function mt:_fastWardCasts(pos, topNode) - for i = self._index, #self._casts do - local action = self._casts[i] - if action.start > pos then - self._index = i - return topNode - end - topNode = topNode:copy() - for _, cast in ipairs(action.casts) do - if cast.mode == '+' then - if cast.optional then - topNode:addOptional() - end - if cast.extends then - topNode:merge(vm.compileNode(cast.extends)) - end - elseif cast.mode == '-' then - if cast.optional then - topNode:removeOptional() - end - if cast.extends then - topNode:removeNode(vm.compileNode(cast.extends)) - end - else - if cast.extends then - topNode:clear() - topNode:merge(vm.compileNode(cast.extends)) - end - end - end - end - self._index = self._index + 1 - return topNode -end - ----@param action parser.object ----@param topNode vm.node ----@param outNode? vm.node ----@return vm.node topNode ----@return vm.node outNode -function mt:_lookIntoChild(action, topNode, outNode) - if not self._has[action] - or self._mark[action] then - return topNode, topNode or outNode - end - self._mark[action] = true - topNode = self:_fastWardCasts(action.start, topNode) - if action.type == 'getlocal' then - if action.node == self._loc then - self._callback(action, topNode) - if outNode then - topNode = topNode:copy():setTruthy() - outNode = outNode:copy():setFalsy() - end - end - elseif action.type == 'function' then - self:_lookIntoBlock(action, topNode:copy()) - elseif action.type == 'unary' then - if not action[1] then - goto RETURN - end - if action.op.type == 'not' then - outNode = outNode or topNode:copy() - outNode, topNode = self:_lookIntoChild(action[1], topNode, outNode) - outNode = outNode:copy() - end - elseif action.type == 'binary' then - if not action[1] or not action[2] then - goto RETURN - end - if action.op.type == 'and' then - topNode = self:_lookIntoChild(action[1], topNode, topNode:copy()) - topNode = self:_lookIntoChild(action[2], topNode, topNode:copy()) - elseif action.op.type == 'or' then - outNode = outNode or topNode:copy() - local topNode1, outNode1 = self:_lookIntoChild(action[1], topNode, outNode) - local topNode2, outNode2 = self:_lookIntoChild(action[2], outNode1, outNode1:copy()) - topNode = vm.createNode(topNode1, topNode2) - outNode = outNode2:copy() - elseif action.op.type == '==' - or action.op.type == '~=' then - local handler, checker - for i = 1, 2 do - if guide.isLiteral(action[i]) then - checker = action[i] - handler = action[3-i] -- Copilot tells me use `3-i` instead of `i%2+1` - end - end - if not handler then - goto RETURN - end - if handler.type == 'getlocal' - and handler.node == self._loc then - -- if x == y then - topNode = self:_lookIntoChild(handler, topNode, outNode) - local checkerNode = vm.compileNode(checker) - if action.op.type == '==' then - topNode = checkerNode - if outNode then - outNode:removeNode(topNode) - end - else - topNode:removeNode(checkerNode) - if outNode then - outNode = checkerNode - end - end - elseif handler.type == 'call' - and checker.type == 'string' - and handler.node.special == 'type' - and handler.args - and handler.args[1] - and handler.args[1].type == 'getlocal' - and handler.args[1].node == self._loc then - -- if type(x) == 'string' then - self:_lookIntoChild(handler, topNode:copy()) - if action.op.type == '==' then - topNode:narrow(checker[1]) - if outNode then - outNode:remove(checker[1]) - end - else - topNode:remove(checker[1]) - if outNode then - outNode:narrow(checker[1]) - end - end - elseif handler.type == 'getlocal' - and checker.type == 'string' then - local nodeValue = vm.getObjectValue(handler.node) - if nodeValue - and nodeValue.type == 'select' - and nodeValue.sindex == 1 then - local call = nodeValue.vararg - if call - and call.type == 'call' - and call.node.special == 'type' - and call.args - and call.args[1] - and call.args[1].type == 'getlocal' - and call.args[1].node == self._loc then - -- `local tp = type(x);if tp == 'string' then` - if action.op.type == '==' then - topNode:narrow(checker[1]) - if outNode then - outNode:remove(checker[1]) - end - else - topNode:remove(checker[1]) - if outNode then - outNode:narrow(checker[1]) - end - end - end - end - end - end - elseif action.type == 'loop' - or action.type == 'in' - or action.type == 'repeat' - or action.type == 'for' then - topNode = self:_lookIntoBlock(action, topNode:copy()) - elseif action.type == 'while' then - local blockNode, mainNode - if action.filter then - blockNode, mainNode = self:_lookIntoChild(action.filter, topNode:copy(), topNode:copy()) - else - blockNode = topNode:copy() - mainNode = topNode:copy() - end - blockNode = self:_lookIntoBlock(action, blockNode:copy()) - topNode = mainNode:merge(blockNode) - if action.filter then - -- look into filter again - guide.eachSource(action.filter, function (src) - self._mark[src] = nil - end) - blockNode, topNode = self:_lookIntoChild(action.filter, topNode:copy(), topNode:copy()) - end - elseif action.type == 'if' then - local hasElse - local mainNode = topNode:copy() - local blockNodes = {} - for _, subBlock in ipairs(action) do - local blockNode = mainNode:copy() - if subBlock.filter then - blockNode, mainNode = self:_lookIntoChild(subBlock.filter, blockNode, mainNode) - else - hasElse = true - mainNode:clear() - end - blockNode = self:_lookIntoBlock(subBlock, blockNode:copy()) - local neverReturn = subBlock.hasReturn - or subBlock.hasGoTo - or subBlock.hasBreak - or subBlock.hasError - if not neverReturn then - blockNodes[#blockNodes+1] = blockNode - end - end - if not hasElse and not topNode:hasKnownType() then - mainNode:merge(vm.declareGlobal('type', 'unknown')) - end - for _, blockNode in ipairs(blockNodes) do - mainNode:merge(blockNode) - end - topNode = mainNode - elseif action.type == 'call' then - if action.node.special == 'assert' and action.args and action.args[1] then - topNode = self:_lookIntoChild(action.args[1], topNode, topNode:copy()) - end - elseif action.type == 'paren' then - topNode, outNode = self:_lookIntoChild(action.exp, topNode, outNode) - elseif action.type == 'setlocal' then - if action.node == self._loc then - if action.value then - self:_lookIntoChild(action.value, topNode) - end - topNode = self._callback(action, topNode) - end - elseif action.type == 'local' then - if action.value - and action.ref - and action.value.type == 'select' then - local index = action.value.sindex - local call = action.value.vararg - if index == 1 - and call.type == 'call' - and call.node - and call.node.special == 'type' - and call.args then - local getLoc = call.args[1] - if getLoc - and getLoc.type == 'getlocal' - and getLoc.node == self._loc then - for _, ref in ipairs(action.ref) do - self:_markHas(ref) - end - end - end - end - end - ::RETURN:: - guide.eachChild(action, function (src) - if self._has[src] then - self:_lookIntoChild(src, topNode) - end - end) - return topNode, outNode or topNode -end - ----@param block parser.object ----@param topNode vm.node ----@return vm.node topNode -function mt:_lookIntoBlock(block, topNode) - if not self._has[block] then - return topNode - end - for _, action in ipairs(block) do - if self._has[action] then - topNode = self:_lookIntoChild(action, topNode) - end - end - topNode = self:_fastWardCasts(block.finish, topNode) - return topNode -end - ----@param loc parser.object ----@param callback vm.runner.callback -function vm.launchRunner(loc, callback) - local main = guide.getParentBlock(loc) - if not main then - return - end - local self = setmetatable({ - _loc = loc, - _casts = {}, - _mark = {}, - _has = {}, - _main = main, - _callback = callback, - }, mt) - - self:_collect() - - self:_lookIntoBlock(main, vm.getNode(loc):copy()) -end diff --git a/script/vm/sign.lua b/script/vm/sign.lua index 7c95fd084..ddc8258f5 100644 --- a/script/vm/sign.lua +++ b/script/vm/sign.lua @@ -3,8 +3,9 @@ local guide = require 'parser.guide' local vm = require 'vm.vm' ---@class vm.sign ----@field parent parser.object ----@field signList vm.node[] +---@field parent parser.object +---@field signList vm.node[] +---@field docGenric parser.object[] local mt = {} mt.__index = mt mt.type = 'sign' @@ -14,34 +15,65 @@ function mt:addSign(node) self.signList[#self.signList+1] = node end +---@param doc parser.object +function mt:addDocGeneric(doc) + self.docGenric[#self.docGenric+1] = doc +end + ---@param uri uri ---@param args parser.object ----@param removeGeneric true? ---@return table? -function mt:resolve(uri, args, removeGeneric) +function mt:resolve(uri, args) if not args then return nil end + + ---@type table local resolved = {} - ---@param object vm.node.object + ---@param object vm.node|vm.node.object ---@param node vm.node local function resolve(object, node) + if object.type == 'vm.node' then + for o in object:eachObject() do + resolve(o, node) + end + return + end + if object.type == 'doc.type' then + ---@cast object parser.object + resolve(vm.compileNode(object), node) + return + end if object.type == 'doc.generic.name' then + ---@type string local key = object[1] if object.literal then -- 'number' -> `T` for n in node:eachObject() do if n.type == 'string' then ---@cast n parser.object - local type = vm.declareGlobal('type', n[1], guide.getUri(n)) + local type = vm.declareGlobal('type', object.pattern and object.pattern:format(n[1]) or n[1], guide.getUri(n)) resolved[key] = vm.createNode(type, resolved[key]) end end else -- number -> T - resolved[key] = vm.createNode(node, resolved[key]) + for n in node:eachObject() do + if n.type ~= 'doc.generic.name' + and n.type ~= 'generic' then + if resolved[key] then + resolved[key]:merge(n) + else + resolved[key] = vm.createNode(n) + end + end + end + if resolved[key] and node:isOptional() then + resolved[key]:addOptional() + end end + return end if object.type == 'doc.type.array' then for n in node:eachObject() do @@ -59,7 +91,7 @@ function mt:resolve(uri, args, removeGeneric) if n.type == 'global' and n.cate == 'type' then -- ---@field [integer]: number -> T[] ---@cast n vm.global - vm.getClassFields(uri, n, vm.declareGlobal('type', 'integer'), false, function (field) + vm.getClassFields(uri, n, vm.declareGlobal('type', 'integer'), function (field) resolve(object.node, vm.compileNode(field.extends)) end) end @@ -68,6 +100,7 @@ function mt:resolve(uri, args, removeGeneric) resolve(object.node, vm.compileNode(n[1])) end end + return end if object.type == 'doc.type.table' then for _, ufield in ipairs(object.fields) do @@ -105,6 +138,36 @@ function mt:resolve(uri, args, removeGeneric) end ::CONTINUE:: end + return + end + if object.type == 'doc.type.function' then + for i, arg in ipairs(object.args) do + if arg.extends then + for n in node:eachObject() do + if n.type == 'function' + or n.type == 'doc.type.function' then + ---@cast n parser.object + local farg = n.args and n.args[i] + if farg then + resolve(arg.extends, vm.compileNode(farg)) + end + end + end + end + end + for i, ret in ipairs(object.returns) do + for n in node:eachObject() do + if n.type == 'function' + or n.type == 'doc.type.function' then + ---@cast n parser.object + local fret = vm.getReturnOfFunction(n, i) + if fret then + resolve(ret, vm.compileNode(fret)) + end + end + end + end + return end end @@ -132,7 +195,11 @@ function mt:resolve(uri, args, removeGeneric) goto CONTINUE end end - local view = vm.viewObject(obj, uri) + if obj.type == 'variable' + or obj.type == 'local' then + goto CONTINUE + end + local view = vm.getInfer(obj):view(uri) if view then knownTypes[view] = true end @@ -158,7 +225,7 @@ function mt:resolve(uri, args, removeGeneric) goto CONTINUE end end - local view = vm.viewObject(n, uri) + local view = vm.getInfer(n):view(uri) if knownTypes[view] then goto CONTINUE end @@ -189,10 +256,8 @@ function mt:resolve(uri, args, removeGeneric) local argNode = vm.compileNode(arg) local knownTypes, genericNames = getSignInfo(sign) if not isAllResolved(genericNames) then - local newArgNode = buildArgNode(argNode,sign, knownTypes) - for n in sign:eachObject() do - resolve(n, newArgNode) - end + local newArgNode = buildArgNode(argNode, sign, knownTypes) + resolve(sign, newArgNode) end end @@ -202,7 +267,77 @@ end ---@return vm.sign function vm.createSign() local genericMgr = setmetatable({ - signList = {}, + signList = {}, + docGenric = {}, }, mt) return genericMgr end + +---@class parser.object +---@field package _sign vm.sign|false|nil + +---@param source parser.object +---@param sign vm.sign +function vm.setSign(source, sign) + source._sign = sign +end + +---@param source parser.object +---@return vm.sign? +function vm.getSign(source) + if source._sign ~= nil then + return source._sign or nil + end + source._sign = false + if source.type == 'function' then + if not source.bindDocs then + return nil + end + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.generic' then + if not source._sign then + source._sign = vm.createSign() + end + source._sign:addDocGeneric(doc) + end + end + if not source._sign then + return nil + 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 nil + end + source._sign = vm.createSign() + if source.type == 'doc.type.function' then + for _, arg in ipairs(source.args) do + if arg.extends then + local argNode = vm.compileNode(arg.extends) + if arg.optional then + argNode:addOptional() + end + source._sign:addSign(argNode) + else + source._sign:addSign(vm.createNode()) + end + end + end + end + return source._sign or nil +end diff --git a/script/vm/tracer.lua b/script/vm/tracer.lua new file mode 100644 index 000000000..e47a98245 --- /dev/null +++ b/script/vm/tracer.lua @@ -0,0 +1,877 @@ +---@class vm +local vm = require 'vm.vm' +local guide = require 'parser.guide' +local util = require 'utility' + +---@class parser.object +---@field package _tracer? vm.tracer +---@field package _casts? parser.object[] + +---@alias tracer.mode 'local' | 'global' + +---@class vm.tracer +---@field mode tracer.mode +---@field name string +---@field source parser.object | vm.variable +---@field assigns (parser.object | vm.variable)[] +---@field assignMap table +---@field getMap table +---@field careMap table +---@field mark table +---@field casts parser.object[] +---@field nodes table +---@field main parser.object +---@field uri uri +---@field castIndex integer? +local mt = {} +mt.__index = mt +mt.fastCalc = true + +---@return parser.object[] +function mt:getCasts() + local root = guide.getRoot(self.main) + if not root._casts then + root._casts = {} + local docs = root.docs + for _, doc in ipairs(docs) do + if doc.type == 'doc.cast' and doc.name then + root._casts[#root._casts+1] = doc + end + end + end + return root._casts +end + +---@param obj parser.object +function mt:collectAssign(obj) + while true do + local block = guide.getParentBlock(obj) + if not block then + return + end + obj = block + if self.assignMap[obj] then + return + end + if obj == self.main then + return + end + self.assignMap[obj] = true + self.assigns[#self.assigns+1] = obj + end +end + +---@param obj parser.object +function mt:collectCare(obj) + while true do + if self.careMap[obj] then + return + end + if obj == self.main then + return + end + if not obj then + return + end + self.careMap[obj] = true + + if self.fastCalc then + if obj.type == 'if' + or obj.type == 'while' + or obj.type == 'binary' then + self.fastCalc = false + end + if obj.type == 'call' and obj.node then + if obj.node.special == 'assert' + or obj.node.special == 'type' then + self.fastCalc = false + end + end + end + + obj = obj.parent + end +end + +function mt:collectLocal() + local startPos = self.source.base.start + local finishPos = 0 + + local variable = self.source + + if variable.base.type ~= 'local' + and variable.base.type ~= 'self' then + self.assigns[#self.assigns+1] = variable + self.assignMap[self.source] = true + end + + for _, set in ipairs(variable.sets) do + self.assigns[#self.assigns+1] = set + self.assignMap[set] = true + self:collectCare(set) + if set.finish > finishPos then + finishPos = set.finish + end + end + + for _, get in ipairs(variable.gets) do + self:collectCare(get) + self.getMap[get] = true + if get.finish > finishPos then + finishPos = get.finish + end + end + + local casts = self:getCasts() + for _, cast in ipairs(casts) do + if cast.name[1] == self.name + and cast.start > startPos + and cast.finish < finishPos + and vm.getCastTargetHead(cast) == variable.base then + self.casts[#self.casts+1] = cast + end + end + + if #self.casts > 0 then + self.fastCalc = false + end +end + +function mt:collectGlobal() + self.assigns[#self.assigns+1] = self.source + self.assignMap[self.source] = true + + local uri = guide.getUri(self.source) + local global = self.source.global + local link = global.links[uri] + + for _, set in ipairs(link.sets) do + self.assigns[#self.assigns+1] = set + self.assignMap[set] = true + self:collectCare(set) + end + + for _, get in ipairs(link.gets) do + self:collectCare(get) + self.getMap[get] = true + end + + local casts = self:getCasts() + for _, cast in ipairs(casts) do + if cast.name[1] == self.name then + local castTarget = vm.getCastTargetHead(cast) + if castTarget and castTarget.type == 'global' then + self.casts[#self.casts+1] = cast + end + end + end + + if #self.casts > 0 then + self.fastCalc = false + end +end + +---@param start integer +---@param finish integer +---@return parser.object? +function mt:getLastAssign(start, finish) + local lastAssign + for _, assign in ipairs(self.assigns) do + local obj + if assign.type == 'variable' then + ---@cast assign vm.variable + obj = assign.base + else + ---@cast assign parser.object + obj = assign + end + if obj.start < start then + goto CONTINUE + end + if (obj.effect or obj.range or obj.start) >= finish then + break + end + local objBlock = guide.getTopBlock(obj) + if not objBlock then + break + end + if objBlock.start <= finish + and objBlock.finish >= finish then + lastAssign = obj + end + ::CONTINUE:: + end + return lastAssign +end + +---@param pos integer +function mt:resetCastsIndex(pos) + for i = 1, #self.casts do + local cast = self.casts[i] + if cast.start > pos then + self.castIndex = i + return + end + end + self.castIndex = nil +end + +---@param pos integer +---@param node vm.node +---@return vm.node +function mt:fastWardCasts(pos, node) + if not self.castIndex then + return node + end + for i = self.castIndex, #self.casts do + local action = self.casts[i] + if action.start > pos then + return node + end + node = node:copy() + for _, cast in ipairs(action.casts) do + if cast.mode == '+' then + if cast.optional then + node:addOptional() + end + if cast.extends then + node:merge(vm.compileNode(cast.extends)) + end + elseif cast.mode == '-' then + if cast.optional then + node:removeOptional() + end + if cast.extends then + node:removeNode(vm.compileNode(cast.extends)) + end + else + if cast.extends then + node:clear() + node:merge(vm.compileNode(cast.extends)) + end + end + end + end + self.castIndex = self.castIndex + 1 + return node +end + +local lookIntoChild = util.switch() + : case 'getlocal' + : case 'getglobal' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + if tracer.getMap[action] then + tracer.nodes[action] = topNode + if outNode then + topNode = topNode:copy():setTruthy() + outNode = outNode:copy():setFalsy() + end + end + return topNode, outNode + end) + : case 'repeat' + : case 'loop' + : case 'for' + : case 'do' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + if action.type == 'loop' then + tracer:lookIntoChild(action.init, topNode) + tracer:lookIntoChild(action.max, topNode) + end + if action[1] then + tracer:lookIntoBlock(action, action.bstart, topNode:copy()) + local lastAssign = tracer:getLastAssign(action.start, action.finish) + if lastAssign then + tracer:getNode(lastAssign) + end + if tracer.nodes[action] then + topNode = tracer.nodes[action]:copy() + end + end + if action.type == 'repeat' then + tracer:lookIntoChild(action.filter, topNode) + end + return topNode, outNode + end) + : case 'in' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.exps, topNode) + if action[1] then + tracer:lookIntoBlock(action, action.bstart, topNode:copy()) + local lastAssign = tracer:getLastAssign(action.start, action.finish) + if lastAssign then + tracer:getNode(lastAssign) + end + if tracer.nodes[action] then + topNode = tracer.nodes[action]:copy() + end + end + return topNode, outNode + end) + : case 'while' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + local blockNode, mainNode + if action.filter then + blockNode, mainNode = tracer:lookIntoChild(action.filter, topNode:copy(), topNode:copy()) + else + blockNode = topNode:copy() + mainNode = topNode:copy() + end + if action[1] then + tracer:lookIntoBlock(action, action.bstart, blockNode:copy()) + local lastAssign = tracer:getLastAssign(action.start, action.finish) + if lastAssign then + tracer:getNode(lastAssign) + end + if tracer.nodes[action] then + topNode = mainNode:merge(tracer.nodes[action]) + end + end + if action.filter then + -- look into filter again + guide.eachSource(action.filter, function (src) + tracer.mark[src] = nil + end) + blockNode, topNode = tracer:lookIntoChild(action.filter, topNode:copy(), topNode:copy()) + end + return topNode, outNode + end) + : case 'if' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + local hasElse + local mainNode = topNode:copy() + local blockNodes = {} + for _, subBlock in ipairs(action) do + tracer:resetCastsIndex(subBlock.start) + local blockNode = mainNode:copy() + if subBlock.filter then + blockNode, mainNode = tracer:lookIntoChild(subBlock.filter, blockNode, mainNode) + else + hasElse = true + mainNode:clear() + end + local mergedNode + if subBlock[1] then + tracer:lookIntoBlock(subBlock, subBlock.bstart, blockNode:copy()) + local neverReturn = subBlock.hasReturn + or subBlock.hasGoTo + or subBlock.hasBreak + or subBlock.hasExit + if neverReturn then + mergedNode = true + else + local lastAssign = tracer:getLastAssign(subBlock.start, subBlock.finish) + if lastAssign then + tracer:getNode(lastAssign) + end + if tracer.nodes[subBlock] then + blockNodes[#blockNodes+1] = tracer.nodes[subBlock] + mergedNode = true + end + end + end + if not mergedNode then + blockNodes[#blockNodes+1] = blockNode + end + end + if not hasElse and not topNode:hasKnownType() then + mainNode:merge(vm.declareGlobal('type', 'unknown')) + end + for _, blockNode in ipairs(blockNodes) do + mainNode:merge(blockNode) + end + topNode = mainNode + return topNode, outNode + end) + : case 'getfield' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.field, topNode) + if tracer.getMap[action] then + tracer.nodes[action] = topNode + if outNode then + topNode = topNode:copy():setTruthy() + outNode = outNode:copy():setFalsy() + end + end + return topNode, outNode + end) + : case 'getmethod' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.method, topNode) + if tracer.getMap[action] then + tracer.nodes[action] = topNode + if outNode then + topNode = topNode:copy():setTruthy() + outNode = outNode:copy():setFalsy() + end + end + return topNode, outNode + end) + : case 'getindex' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.index, topNode) + if tracer.getMap[action] then + tracer.nodes[action] = topNode + if outNode then + topNode = topNode:copy():setTruthy() + outNode = outNode:copy():setFalsy() + end + end + return topNode, outNode + end) + : case 'setfield' + : case 'setmethod' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.value, topNode) + return topNode, outNode + end) + : case 'setglobal' + : case 'setlocal' + : case 'tablefield' + : case 'tableexp' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.value, topNode) + return topNode, outNode + end) + : case 'setindex' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.index, topNode) + tracer:lookIntoChild(action.value, topNode) + return topNode, outNode + end) + : case 'tableindex' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.index, topNode) + tracer:lookIntoChild(action.value, topNode) + return topNode, outNode + end) + : case 'local' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.value, topNode) + -- special treat for `local tp = type(x)` + if action.value + and action.ref + and action.value.type == 'select' then + local index = action.value.sindex + local call = action.value.vararg + if index == 1 + and call.type == 'call' + and call.node + and call.node.special == 'type' + and call.args then + local getVar = call.args[1] + if getVar + and tracer.getMap[getVar] then + for _, ref in ipairs(action.ref) do + tracer:collectCare(ref) + end + end + end + end + return topNode, outNode + end) + : case 'return' + : case 'table' + : case 'callargs' + : case 'list' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + for _, ret in ipairs(action) do + tracer:lookIntoChild(ret, topNode:copy()) + end + return topNode, outNode + end) + : case 'select' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoChild(action.vararg, topNode) + return topNode, outNode + end) + : case 'function' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + tracer:lookIntoBlock(action, action.bstart, topNode:copy()) + return topNode, outNode + end) + : case 'paren' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + topNode, outNode = tracer:lookIntoChild(action.exp, topNode, outNode) + return topNode, outNode + end) + : case 'call' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + if action.node.special == 'assert' and action.args and action.args[1] then + for i = 2, #action.args do + tracer:lookIntoChild(action.args[i], topNode, topNode:copy()) + end + topNode = tracer:lookIntoChild(action.args[1], topNode:copy(), topNode:copy()) + end + tracer:lookIntoChild(action.node, topNode) + tracer:lookIntoChild(action.args, topNode) + return topNode, outNode + end) + : case 'binary' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + if not action[1] or not action[2] then + tracer:lookIntoChild(action[1], topNode) + tracer:lookIntoChild(action[2], topNode) + return topNode, outNode + end + if action.op.type == 'and' then + topNode = tracer:lookIntoChild(action[1], topNode, topNode:copy()) + topNode = tracer:lookIntoChild(action[2], topNode, topNode:copy()) + elseif action.op.type == 'or' then + outNode = outNode or topNode:copy() + local topNode1, outNode1 = tracer:lookIntoChild(action[1], topNode, outNode) + local topNode2, outNode2 = tracer:lookIntoChild(action[2], outNode1, outNode1:copy()) + topNode = vm.createNode(topNode1, topNode2) + outNode = outNode2:copy() + elseif action.op.type == '==' + or action.op.type == '~=' then + local handler, checker + for i = 1, 2 do + if guide.isLiteral(action[i]) then + checker = action[i] + handler = action[3-i] -- Copilot tells me use `3-i` instead of `i%2+1` + end + end + if not handler then + tracer:lookIntoChild(action[1], topNode) + tracer:lookIntoChild(action[2], topNode) + return topNode, outNode + end + if tracer.getMap[handler] then + -- if x == y then + topNode = tracer:lookIntoChild(handler, topNode, outNode) + local checkerNode = vm.compileNode(checker) + local checkerName = vm.getNodeName(checker) + if checkerName then + topNode = topNode:copy() + if action.op.type == '==' then + topNode:narrow(tracer.uri, checkerName) + if outNode then + outNode:removeNode(checkerNode) + end + else + topNode:removeNode(checkerNode) + if outNode then + outNode:narrow(tracer.uri, checkerName) + end + end + end + elseif handler.type == 'call' + and checker.type == 'string' + and handler.node.special == 'type' + and handler.args + and handler.args[1] + and tracer.getMap[handler.args[1]] then + -- if type(x) == 'string' then + tracer:lookIntoChild(handler, topNode) + topNode = topNode:copy() + if action.op.type == '==' then + topNode:narrow(tracer.uri, checker[1]) + if outNode then + outNode:remove(checker[1]) + end + else + topNode:remove(checker[1]) + if outNode then + outNode:narrow(tracer.uri, checker[1]) + end + end + elseif handler.type == 'getlocal' + and checker.type == 'string' then + -- `local tp = type(x);if tp == 'string' then` + local nodeValue = vm.getObjectValue(handler.node) + if nodeValue + and nodeValue.type == 'select' + and nodeValue.sindex == 1 then + local call = nodeValue.vararg + if call + and call.type == 'call' + and call.node.special == 'type' + and call.args + and tracer.getMap[call.args[1]] then + if action.op.type == '==' then + topNode:narrow(tracer.uri, checker[1]) + if outNode then + outNode:remove(checker[1]) + end + else + topNode:remove(checker[1]) + if outNode then + outNode:narrow(tracer.uri, checker[1]) + end + end + end + end + end + end + tracer:lookIntoChild(action[1], topNode) + tracer:lookIntoChild(action[2], topNode) + return topNode, outNode + end) + : case 'unary' + ---@param tracer vm.tracer + ---@param action parser.object + ---@param topNode vm.node + ---@param outNode? vm.node + : call(function (tracer, action, topNode, outNode) + if not action[1] then + tracer:lookIntoChild(action[1], topNode) + return topNode, outNode + end + if action.op.type == 'not' then + outNode = outNode or topNode:copy() + outNode, topNode = tracer:lookIntoChild(action[1], topNode, outNode) + outNode = outNode:copy() + end + tracer:lookIntoChild(action[1], topNode) + return topNode, outNode + end) + +---@param action parser.object +---@param topNode vm.node +---@param outNode? vm.node +---@return vm.node topNode +---@return vm.node outNode +function mt:lookIntoChild(action, topNode, outNode) + if not self.careMap[action] + or self.mark[action] then + return topNode, outNode or topNode + end + self.mark[action] = true + topNode = self:fastWardCasts(action.start, topNode) + topNode, outNode = lookIntoChild(action.type, self, action, topNode, outNode) + return topNode, outNode or topNode +end + +---@param block parser.object +---@param start integer +---@param node vm.node +function mt:lookIntoBlock(block, start, node) + self:resetCastsIndex(start) + for _, action in ipairs(block) do + if (action.effect or action.start) < start then + goto CONTINUE + end + if self.careMap[action] then + node = self:lookIntoChild(action, node) + if action.type == 'do' + or action.type == 'loop' + or action.type == 'in' + or action.type == 'repeat' then + return + end + end + if action.finish > start and self.assignMap[action] then + return + end + ::CONTINUE:: + end + self.nodes[block] = node + if block.type == 'repeat' then + self:lookIntoChild(block.filter, node) + end + if block.type == 'do' + or block.type == 'loop' + or block.type == 'in' + or block.type == 'repeat' then + self:lookIntoBlock(block.parent, block.finish, node) + end +end + +---@param source parser.object +function mt:calcNode(source) + if self.getMap[source] then + local lastAssign = self:getLastAssign(0, source.finish) + if not lastAssign then + return + end + if self.fastCalc then + self.nodes[source] = vm.compileNode(lastAssign) + return + end + self:calcNode(lastAssign) + return + end + if self.assignMap[source] then + local node = vm.compileNode(source) + self.nodes[source] = node + local parentBlock = guide.getParentBlock(source) + if parentBlock then + self:lookIntoBlock(parentBlock, source.finish, node) + end + return + end +end + +---@param source parser.object +---@return vm.node? +function mt:getNode(source) + local cache = self.nodes[source] + if cache ~= nil then + return cache or nil + end + if source == self.main then + self.nodes[source] = false + return nil + end + self.nodes[source] = false + self:calcNode(source) + return self.nodes[source] or nil +end + +---@class vm.node +---@field package _tracer vm.tracer + +---@param mode tracer.mode +---@param source parser.object | vm.variable +---@param name string +---@return vm.tracer? +local function createTracer(mode, source, name) + local node = vm.compileNode(source) + local tracer = node._tracer + if tracer then + return tracer + end + local main + if source.type == 'variable' then + ---@cast source vm.variable + main = guide.getParentBlock(source.base) + else + ---@cast source parser.object + main = guide.getParentBlock(source) + end + if not main then + return nil + end + tracer = setmetatable({ + source = source, + mode = mode, + name = name, + assigns = {}, + assignMap = {}, + getMap = {}, + careMap = {}, + mark = {}, + casts = {}, + nodes = {}, + main = main, + uri = guide.getUri(main), + }, mt) + node._tracer = tracer + + if tracer.mode == 'local' then + tracer:collectLocal() + else + tracer:collectGlobal() + end + + return tracer +end + +---@param source parser.object +---@return vm.node? +function vm.traceNode(source) + local mode, base, name + if vm.getGlobalNode(source) then + base = vm.getGlobalBase(source) + if not base then + return nil + end + mode = 'global' + name = base.global:getCodeName() + else + base = vm.getVariable(source) + if not base then + return nil + end + name = base:getCodeName() + mode = 'local' + end + local tracer = createTracer(mode, base, name) + if not tracer then + return nil + end + local node = tracer:getNode(source) + return node +end diff --git a/script/vm/type.lua b/script/vm/type.lua index d112be2c9..545d2de59 100644 --- a/script/vm/type.lua +++ b/script/vm/type.lua @@ -3,10 +3,17 @@ local vm = require 'vm.vm' local guide = require 'parser.guide' local config = require 'config.config' local util = require 'utility' +local lang = require 'language' + +---@class vm.ANY +---@diagnostic disable-next-line: assign-type-mismatch +vm.ANY = debug.upvalueid(require, 1) + +---@alias typecheck.err vm.node.object|string|vm.node ---@param object vm.node.object ---@return string? -local function getNodeName(object) +function vm.getNodeName(object) if object.type == 'global' and object.cate == 'type' then ---@cast object vm.global return object.name @@ -38,95 +45,299 @@ local function getNodeName(object) if object.type == 'doc.type.string' then return 'string' end + if object.type == 'doc.field.name' then + return 'string' + end return nil end ---@param parentName string ---@param child vm.node.object ---@param uri uri +---@param mark table +---@param errs? typecheck.err[] ---@return boolean? -local function checkEnum(parentName, child, uri) +local function checkParentEnum(parentName, child, uri, mark, errs) local parentClass = vm.getGlobal('type', parentName) if not parentClass then return nil end + local enums for _, set in ipairs(parentClass:getSets(uri)) do if set.type == 'doc.enum' then - if not set._enums then - return false + enums = vm.getEnums(set) + break + end + end + if not enums then + return nil + end + if child.type == 'global' then + ---@cast child vm.global + for _, enum in ipairs(enums) do + if vm.isSubType(uri, child, vm.compileNode(enum), mark) then + return true end - if child.type ~= 'string' - and child.type ~= 'doc.type.string' - and child.type ~= 'integer' - and child.type ~= 'number' - and child.type ~= 'doc.type.integer' then - return false + end + if errs then + errs[#errs+1] = 'TYPE_ERROR_ENUM_GLOBAL_DISMATCH' + errs[#errs+1] = child + errs[#errs+1] = parentClass + end + return false + elseif child.type == 'generic' then + ---@cast child vm.generic + if errs then + errs[#errs+1] = 'TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED' + errs[#errs+1] = child + end + return false + else + ---@cast child parser.object + local childName = vm.getNodeName(child) + if childName == 'number' + or childName == 'integer' + or childName == 'boolean' + or childName == 'string' then + for _, enum in ipairs(enums) do + for nd in vm.compileNode(enum):eachObject() do + if childName == vm.getNodeName(nd) and nd[1] == child[1] then + return true + end + end end - return util.arrayHas(set._enums, child[1]) + if errs then + errs[#errs+1] = 'TYPE_ERROR_ENUM_LITERAL_DISMATCH' + errs[#errs+1] = child[1] + errs[#errs+1] = parentClass + end + return false + elseif childName == 'function' + or childName == 'table' then + for _, enum in ipairs(enums) do + for nd in vm.compileNode(enum):eachObject() do + if child == nd then + return true + end + end + end + if errs then + errs[#errs+1] = 'TYPE_ERROR_ENUM_OBJECT_DISMATCH' + errs[#errs+1] = child + errs[#errs+1] = parentClass + end + return false end + if errs then + errs[#errs+1] = 'TYPE_ERROR_ENUM_NO_OBJECT' + errs[#errs+1] = child + end + return false end +end - return nil +---@param childName string +---@param parent vm.node.object +---@param uri uri +---@param mark table +---@param errs? typecheck.err[] +---@return boolean? +local function checkChildEnum(childName, parent , uri, mark, errs) + if mark[childName] then + return + end + local childClass = vm.getGlobal('type', childName) + if not childClass then + return nil + end + local enums + for _, set in ipairs(childClass:getSets(uri)) do + if set.type == 'doc.enum' then + enums = vm.getEnums(set) + break + end + end + if not enums then + return nil + end + mark[childName] = true + for _, enum in ipairs(enums) do + if not vm.isSubType(uri, vm.compileNode(enum), parent, mark ,errs) then + mark[childName] = nil + return false + end + end + mark[childName] = nil + return true end ---@param parent vm.node.object ---@param child vm.node.object +---@param mark table +---@param errs? typecheck.err[] ---@return boolean -local function checkValue(parent, child) +local function checkValue(parent, child, mark, errs) if parent.type == 'doc.type.integer' then if child.type == 'integer' or child.type == 'doc.type.integer' or child.type == 'number' then - return parent[1] == child[1] + if parent[1] ~= child[1] then + if errs then + errs[#errs+1] = 'TYPE_ERROR_INTEGER_DISMATCH' + errs[#errs+1] = child[1] + errs[#errs+1] = parent[1] + end + return false + end end - elseif parent.type == 'doc.type.string' then + return true + end + + if parent.type == 'doc.type.string' + or parent.type == 'doc.field.name' then if child.type == 'string' - or child.type == 'doc.type.string' then - return parent[1] == child[1] + or child.type == 'doc.type.string' + or child.type == 'doc.field.name' then + if parent[1] ~= child[1] then + if errs then + errs[#errs+1] = 'TYPE_ERROR_STRING_DISMATCH' + errs[#errs+1] = child[1] + errs[#errs+1] = parent[1] + end + return false + end end + return true + end + + if parent.type == 'doc.type.boolean' then + if child.type == 'boolean' + or child.type == 'doc.type.boolean' then + if parent[1] ~= child[1] then + if errs then + errs[#errs+1] = 'TYPE_ERROR_BOOLEAN_DISMATCH' + errs[#errs+1] = child[1] + errs[#errs+1] = parent[1] + end + return false + end + end + return true + end + + if parent.type == 'doc.type.table' then + if child.type == 'doc.type.table' then + if child == parent then + return true + end + ---@cast parent parser.object + ---@cast child parser.object + local uri = guide.getUri(parent) + local tnode = vm.compileNode(child) + for _, pfield in ipairs(parent.fields) do + local knode = vm.compileNode(pfield.name) + local cvalues = vm.getTableValue(uri, tnode, knode, true) + if not cvalues then + if errs then + errs[#errs+1] = 'TYPE_ERROR_TABLE_NO_FIELD' + errs[#errs+1] = pfield.name + end + return false + end + local pvalues = vm.compileNode(pfield.extends) + if vm.isSubType(uri, cvalues, pvalues, mark, errs) == false then + if errs then + errs[#errs+1] = 'TYPE_ERROR_TABLE_FIELD_DISMATCH' + errs[#errs+1] = pfield.name + errs[#errs+1] = cvalues + errs[#errs+1] = pvalues + end + return false + end + end + end + return true end return true end +---@param name string +---@param suri uri +---@return boolean +local function isAlias(name, suri) + local global = vm.getGlobal('type', name) + if not global then + return false + end + for _, set in ipairs(global:getSets(suri)) do + if set.type == 'doc.alias' then + return true + end + end + return false +end + ---@param uri uri ---@param child vm.node|string|vm.node.object ---@param parent vm.node|string|vm.node.object ---@param mark? table ----@return boolean -function vm.isSubType(uri, child, parent, mark) +---@param errs? typecheck.err[] +---@return boolean|nil +function vm.isSubType(uri, child, parent, mark, errs) mark = mark or {} if type(child) == 'string' then local global = vm.getGlobal('type', child) if not global then - return false + return nil end child = global elseif child.type == 'vm.node' then if config.get(uri, 'Lua.type.weakUnionCheck') then - local hasKnownType + local hasKnownType = 0 for n in child:eachObject() do - if getNodeName(n) then - hasKnownType = true - if vm.isSubType(uri, n, parent, mark) then + if vm.getNodeName(n) then + local res = vm.isSubType(uri, n, parent, mark, errs) + if res == true then return true + elseif res == false then + hasKnownType = hasKnownType + 1 end end end - return not hasKnownType + if hasKnownType > 0 then + if errs + and hasKnownType > 1 + and #vm.getInfer(child):getSubViews(uri) > 1 then + errs[#errs+1] = 'TYPE_ERROR_CHILD_ALL_DISMATCH' + errs[#errs+1] = child + errs[#errs+1] = parent + end + return false + end + return true else - local weakNil = config.get(uri, 'Lua.type.weakNilCheck') + local weakNil = config.get(uri, 'Lua.type.weakNilCheck') for n in child:eachObject() do - local nodeName = getNodeName(n) + local nodeName = vm.getNodeName(n) if nodeName and not (nodeName == 'nil' and weakNil) - and not vm.isSubType(uri, n, parent, mark) then + and vm.isSubType(uri, n, parent, mark, errs) == false then + if errs then + errs[#errs+1] = 'TYPE_ERROR_UNION_DISMATCH' + errs[#errs+1] = n + errs[#errs+1] = parent + end return false end end if not weakNil and child:isOptional() then - if not vm.isSubType(uri, 'nil', parent, mark) then + if vm.isSubType(uri, 'nil', parent, mark, errs) == false then + if errs then + errs[#errs+1] = 'TYPE_ERROR_OPTIONAL_DISMATCH' + errs[#errs+1] = parent + end return false end end @@ -134,6 +345,18 @@ function vm.isSubType(uri, child, parent, mark) end end + ---@cast child vm.node.object + local childName = vm.getNodeName(child) + if childName == 'any' + or childName == 'unknown' then + return true + end + + if not childName + or isAlias(childName, uri) then + return nil + end + if type(parent) == 'string' then local global = vm.getGlobal('type', parent) if not global then @@ -141,39 +364,53 @@ function vm.isSubType(uri, child, parent, mark) end parent = global elseif parent.type == 'vm.node' then + local hasKnownType = 0 for n in parent:eachObject() do - if getNodeName(n) - and vm.isSubType(uri, child, n, mark) then - return true + if vm.getNodeName(n) then + local res = vm.isSubType(uri, child, n, mark, errs) + if res == true then + return true + elseif res == false then + hasKnownType = hasKnownType + 1 + end end if n.type == 'doc.generic.name' then return true end end if parent:isOptional() then - if vm.isSubType(uri, child, 'nil', mark) then + if vm.isSubType(uri, child, 'nil', mark, errs) == true then return true end end - return false + if hasKnownType > 0 then + if errs + and hasKnownType > 1 + and #vm.getInfer(parent):getSubViews(uri) > 1 then + errs[#errs+1] = 'TYPE_ERROR_PARENT_ALL_DISMATCH' + errs[#errs+1] = child + errs[#errs+1] = parent + end + return false + end + return true end - ---@cast child vm.node.object ---@cast parent vm.node.object - local childName = getNodeName(child) - local parentName = getNodeName(parent) - if childName == 'any' - or parentName == 'any' - or childName == 'unknown' - or parentName == 'unknown' - or not childName - or not parentName then + local parentName = vm.getNodeName(parent) + if parentName == 'any' + or parentName == 'unknown' then return true end + if not parentName + or isAlias(parentName, uri) then + return nil + end + if childName == parentName then - if not checkValue(parent, child) then + if not checkValue(parent, child, mark, errs) then return false end return true @@ -190,21 +427,32 @@ function vm.isSubType(uri, child, parent, mark) if child.type == 'number' and child[1] and not math.tointeger(child[1]) then + if errs then + errs[#errs+1] = 'TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER' + errs[#errs+1] = child[1] + end return false end if child.type == 'global' and child.cate == 'type' then + if errs then + errs[#errs+1] = 'TYPE_ERROR_NUMBER_TYPE_TO_INTEGER' + end return false end return true end - local isEnum = checkEnum(parentName, child, uri) - if isEnum ~= nil then - return isEnum + local result = checkParentEnum(parentName, child, uri, mark, errs) + if result ~= nil then + return result + end + + result = checkChildEnum(childName, parent, uri, mark, errs) + if result ~= nil then + return result end - -- TODO: check duck if parentName == 'table' and not guide.isBasicType(childName) then return true end @@ -223,15 +471,12 @@ function vm.isSubType(uri, child, parent, mark) for _, ext in ipairs(set.extends) do if ext.type == 'doc.extends.name' and (not isBasicType or guide.isBasicType(ext[1])) - and vm.isSubType(uri, ext[1], parent, mark) then + and vm.isSubType(uri, ext[1], parent, mark, errs) == true then + mark[childName] = nil return true end end end - if set.type == 'doc.alias' - or set.type == 'doc.enum' then - return true - end end end mark[childName] = nil @@ -245,10 +490,26 @@ function vm.isSubType(uri, child, parent, mark) ]] if guide.isBasicType(childName) and guide.isLiteral(child) - and vm.isSubType(uri, parentName, childName) then + and vm.isSubType(uri, parentName, childName, mark) then return true end + if errs then + errs[#errs+1] = 'TYPE_ERROR_DISMATCH' + errs[#errs+1] = child + errs[#errs+1] = parent + end + return false +end + +---@param node string|vm.node|vm.object +function vm.isUnknown(node) + if type(node) == 'string' then + return node == 'unknown' + end + if node.type == 'vm.node' then + return not node:hasKnownType() + end return false end @@ -262,8 +523,7 @@ function vm.getTableValue(uri, tnode, knode, inversion) for tn in tnode:eachObject() do if tn.type == 'doc.type.table' then for _, field in ipairs(tn.fields) do - if field.name.type ~= 'doc.field.name' - and field.extends then + if field.extends then if inversion then if vm.isSubType(uri, vm.compileNode(field.name), knode) then result:merge(vm.compileNode(field.extends)) @@ -280,6 +540,9 @@ function vm.getTableValue(uri, tnode, knode, inversion) result:merge(vm.compileNode(tn.node)) end if tn.type == 'table' then + if vm.isUnknown(knode) then + goto CONTINUE + end for _, field in ipairs(tn) do if field.type == 'tableindex' and field.value then @@ -315,6 +578,7 @@ function vm.getTableValue(uri, tnode, knode, inversion) end end end + ::CONTINUE:: end if result:isEmpty() then return nil @@ -350,6 +614,9 @@ function vm.getTableKey(uri, tnode, vnode, reverse) result:merge(vm.declareGlobal('type', 'integer')) end if tn.type == 'table' then + if vm.isUnknown(tnode) then + goto CONTINUE + end for _, field in ipairs(tn) do if field.type == 'tableindex' then if field.index then @@ -364,6 +631,7 @@ function vm.getTableKey(uri, tnode, vnode, reverse) end end end + ::CONTINUE:: end if result:isEmpty() then return nil @@ -374,8 +642,9 @@ end ---@param uri uri ---@param defNode vm.node ---@param refNode vm.node +---@param errs typecheck.err[]? ---@return boolean -function vm.canCastType(uri, defNode, refNode) +function vm.canCastType(uri, defNode, refNode, errs) local defInfer = vm.getInfer(defNode) local refInfer = vm.getInfer(refNode) @@ -391,6 +660,9 @@ function vm.canCastType(uri, defNode, refNode) if refInfer:view(uri) == 'unknown' then return true end + if defInfer:view(uri) == 'nil' then + return true + end if vm.isSubType(uri, refNode, 'nil') then -- allow `local x = {};x = nil`, @@ -410,9 +682,110 @@ function vm.canCastType(uri, defNode, refNode) end end - if vm.isSubType(uri, refNode, defNode) then + if vm.isSubType(uri, refNode, defNode, {}, errs) then return true end return false end + +local ErrorMessageMap = { + TYPE_ERROR_ENUM_GLOBAL_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_ENUM_GENERIC_UNSUPPORTED = {'child'}, + TYPE_ERROR_ENUM_LITERAL_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_ENUM_OBJECT_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_ENUM_NO_OBJECT = {'child'}, + TYPE_ERROR_INTEGER_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_STRING_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_BOOLEAN_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_TABLE_NO_FIELD = {'key'}, + TYPE_ERROR_TABLE_FIELD_DISMATCH = {'key', 'child', 'parent'}, + TYPE_ERROR_CHILD_ALL_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_PARENT_ALL_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_UNION_DISMATCH = {'child', 'parent'}, + TYPE_ERROR_OPTIONAL_DISMATCH = {'parent'}, + TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = {'child'}, + TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = {}, + TYPE_ERROR_DISMATCH = {'child', 'parent'}, +} + +---@param uri uri +---@param errs typecheck.err[] +---@return string +function vm.viewTypeErrorMessage(uri, errs) + local lines = {} + local mark = {} + local index = 1 + while true do + local name = errs[index] + if not name then + break + end + index = index + 1 + local params = ErrorMessageMap[name] + local lparams = {} + for _, paramName in ipairs(params) do + local value = errs[index] + if type(value) == 'string' + or type(value) == 'number' + or type(value) == 'boolean' then + lparams[paramName] = util.viewLiteral(value) + elseif value.type == 'global' then + lparams[paramName] = value.name + elseif value.type == 'vm.node' then + ---@cast value vm.node + lparams[paramName] = vm.getInfer(value):view(uri) + elseif value.type == 'table' then + lparams[paramName] = 'table' + elseif value.type == 'generic' then + ---@cast value vm.generic + lparams[paramName] = vm.getInfer(value):view(uri) + elseif value.type == 'variable' then + else + ---@cast value -string, -vm.global, -vm.node, -vm.generic, -vm.variable + if paramName == 'key' then + lparams[paramName] = vm.viewKey(value, uri) + else + lparams[paramName] = vm.getInfer(value):view(uri) + or vm.getInfer(value):view(uri) + end + end + index = index + 1 + end + local line = lang.script(name, lparams) + if not mark[line] then + mark[line] = true + lines[#lines+1] = '- ' .. line + end + end + util.revertArray(lines) + if #lines > 15 then + lines[13] = ('...(+%d)'):format(#lines - 15) + table.move(lines, #lines - 2, #lines, 14) + return table.concat(lines, '\n', 1, 16) + else + return table.concat(lines, '\n') + end +end + +---@param name string +---@param uri uri +---@return parser.object[]? +function vm.getOverloadsByTypeName(name, uri) + local global = vm.getGlobal('type', name) + if not global then + return nil + end + local results + for _, set in ipairs(global:getSets(uri)) do + for _, doc in ipairs(set.bindGroup) do + if doc.type == 'doc.overload' then + if not results then + results = {} + end + results[#results+1] = doc.overload + end + end + end + return results +end diff --git a/script/vm/variable.lua b/script/vm/variable.lua new file mode 100644 index 000000000..150ad18b5 --- /dev/null +++ b/script/vm/variable.lua @@ -0,0 +1,411 @@ +local util = require 'utility' +local guide = require 'parser.guide' +---@class vm +local vm = require 'vm.vm' + +---@class vm.variable +---@field uri uri +---@field root parser.object +---@field id string +---@field base parser.object +---@field sets parser.object[] +---@field gets parser.object[] +local mt = {} +mt.__index = mt +mt.type = 'variable' + +---@param id string +---@return vm.variable +local function createVariable(root, id) + local variable = setmetatable({ + root = root, + uri = root.uri, + id = id, + sets = {}, + gets = {}, + }, mt) + return variable +end + +---@class parser.object +---@field package _variableNode vm.variable|false +---@field package _variableNodes table + +local compileVariables, getLoc + +---@param id string +---@param source parser.object +---@param base parser.object +---@return vm.variable +local function insertVariableID(id, source, base) + local root = guide.getRoot(source) + if not root._variableNodes then + root._variableNodes = util.multiTable(2, function (lid) + local variable = createVariable(root, lid) + return variable + end) + end + local variable = root._variableNodes[id] + variable.base = base + if guide.isAssign(source) then + variable.sets[#variable.sets+1] = source + else + variable.gets[#variable.gets+1] = source + end + return variable +end + +local compileSwitch = util.switch() + : case 'local' + : case 'self' + : call(function (source, base) + local id = ('%d'):format(source.start) + local variable = insertVariableID(id, source, base) + source._variableNode = variable + if not source.ref then + return + end + for _, ref in ipairs(source.ref) do + compileVariables(ref, base) + end + end) + : case 'setlocal' + : case 'getlocal' + : call(function (source, base) + local id = ('%d'):format(source.node.start) + local variable = insertVariableID(id, source, base) + source._variableNode = variable + compileVariables(source.next, base) + end) + : case 'getfield' + : case 'setfield' + : call(function (source, base) + local parentNode = source.node._variableNode + if not parentNode then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + local id = parentNode.id .. vm.ID_SPLITE .. key + local variable = insertVariableID(id, source, base) + source._variableNode = variable + source.field._variableNode = variable + if source.type == 'getfield' then + compileVariables(source.next, base) + end + end) + : case 'getmethod' + : case 'setmethod' + : call(function (source, base) + local parentNode = source.node._variableNode + if not parentNode then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + local id = parentNode.id .. vm.ID_SPLITE .. key + local variable = insertVariableID(id, source, base) + source._variableNode = variable + source.method._variableNode = variable + if source.type == 'getmethod' then + compileVariables(source.next, base) + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source, base) + local parentNode = source.node._variableNode + if not parentNode then + return + end + local key = guide.getKeyName(source) + if type(key) ~= 'string' then + return + end + local id = parentNode.id .. vm.ID_SPLITE .. key + local variable = insertVariableID(id, source, base) + source._variableNode = variable + source.index._variableNode = variable + if source.type == 'setindex' then + compileVariables(source.next, base) + end + end) + +local leftSwitch = util.switch() + : case 'field' + : case 'method' + : call(function (source) + return getLoc(source.parent) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source) + return getLoc(source.node) + end) + : case 'getlocal' + : call(function (source) + return source.node + end) + : case 'local' + : case 'self' + : call(function (source) + return source + end) + +---@param source parser.object +---@return parser.object? +function getLoc(source) + return leftSwitch(source.type, source) +end + +---@return parser.object +function mt:getBase() + return self.base +end + +---@return string +function mt:getCodeName() + local name = self.id:gsub(vm.ID_SPLITE, '.'):gsub('^%d+', self.base[1]) + return name +end + +---@return vm.variable? +function mt:getParent() + local parentID = self.id:match('^(.+)' .. vm.ID_SPLITE) + if not parentID then + return nil + end + return self.root._variableNodes[parentID] +end + +---@return string? +function mt:getFieldName() + return self.id:match(vm.ID_SPLITE .. '(.-)$') +end + +---@param key? string +function mt:getSets(key) + if not key then + return self.sets + end + local id = self.id .. vm.ID_SPLITE .. key + local variable = self.root._variableNodes[id] + return variable.sets +end + +---@param includeGets boolean? +function mt:getFields(includeGets) + local id = self.id + local root = self.root + -- TODO๏ผšoptimize + local clock = os.clock() + local fields = {} + for lid, variable in pairs(root._variableNodes) do + if lid ~= id + and util.stringStartWith(lid, id) + and lid:sub(#id + 1, #id + 1) == vm.ID_SPLITE + -- only one field + and not lid:find(vm.ID_SPLITE, #id + 2) then + for _, src in ipairs(variable.sets) do + fields[#fields+1] = src + end + if includeGets then + for _, src in ipairs(variable.gets) do + fields[#fields+1] = src + end + end + end + end + local cost = os.clock() - clock + if cost > 1.0 then + log.warn('variable-id getFields takes %.3f seconds', cost) + end + return fields +end + +---@param source parser.object +---@param base parser.object +function compileVariables(source, base) + if not source then + return + end + source._variableNode = false + if not compileSwitch:has(source.type) then + return + end + compileSwitch(source.type, source, base) +end + +---@param source parser.object +---@return string? +function vm.getVariableID(source) + local variable = vm.getVariableNode(source) + if not variable then + return nil + end + return variable.id +end + +---@param source parser.object +---@param key? string +---@return vm.variable? +function vm.getVariable(source, key) + local variable = vm.getVariableNode(source) + if not variable then + return nil + end + if not key then + return variable + end + local root = guide.getRoot(source) + if not root._variableNodes then + return nil + end + local id = variable.id .. vm.ID_SPLITE .. key + return root._variableNodes[id] +end + +---@param source parser.object +---@return vm.variable? +function vm.getVariableNode(source) + local variable = source._variableNode + if variable ~= nil then + return variable or nil + end + + source._variableNode = false + local loc = getLoc(source) + if not loc then + return nil + end + compileVariables(loc, loc) + return source._variableNode or nil +end + +---@param source parser.object +---@param name string +---@return vm.variable? +function vm.getVariableInfoByCodeName(source, name) + local id = vm.getVariableID(source) + if not id then + return nil + end + local root = guide.getRoot(source) + if not root._variableNodes then + return nil + end + local headPos = name:find('.', 1, true) + if not headPos then + return root._variableNodes[id] + end + local vid = id .. name:sub(headPos):gsub('%.', vm.ID_SPLITE) + return root._variableNodes[vid] +end + +---@param source parser.object +---@param key? string +---@return parser.object[]? +function vm.getVariableSets(source, key) + local variable = vm.getVariable(source, key) + if not variable then + return nil + end + return variable.sets +end + +---@param source parser.object +---@param key? string +---@return parser.object[]? +function vm.getVariableGets(source, key) + local variable = vm.getVariable(source, key) + if not variable then + return nil + end + return variable.gets +end + +---@param source parser.object +---@param includeGets boolean +---@return parser.object[]? +function vm.getVariableFields(source, includeGets) + local variable = vm.getVariable(source) + if not variable then + return nil + end + return variable:getFields(includeGets) +end + +---@param source parser.object +---@return boolean +function vm.compileByVariable(source) + local variable = vm.getVariableNode(source) + if not variable then + return false + end + vm.setNode(source, variable) + return true +end + +---@param source parser.object +local function compileSelf(source) + if source.parent.type ~= 'funcargs' then + return + end + ---@type parser.object + local node = source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node + if not node then + return + end + local fields = vm.getVariableFields(source, false) + if not fields then + return + end + local variableNode = vm.getVariableNode(node) + local globalNode = vm.getGlobalNode(node) + if not variableNode and not globalNode then + return + end + for _, field in ipairs(fields) do + if field.type == 'setfield' then + local key = guide.getKeyName(field) + if key then + if variableNode then + local myID = variableNode.id .. vm.ID_SPLITE .. key + insertVariableID(myID, field, variableNode.base) + end + if globalNode then + local myID = globalNode:getName() .. vm.ID_SPLITE .. key + local myGlobal = vm.declareGlobal('variable', myID, guide.getUri(node)) + myGlobal:addSet(guide.getUri(node), field) + end + end + end + end +end + +---@param source parser.object +local function compileAst(source) + --[[ + local mt + function mt:xxx() + self.a = 1 + end + + mt.a --> find this definition + ]] + guide.eachSourceType(source, 'self', function (src) + compileSelf(src) + end) +end + +return { + compileAst = compileAst, +} diff --git a/script/vm/visible.lua b/script/vm/visible.lua new file mode 100644 index 000000000..0f486d6bb --- /dev/null +++ b/script/vm/visible.lua @@ -0,0 +1,187 @@ +---@class vm +local vm = require 'vm.vm' +local guide = require 'parser.guide' +local config = require 'config' +local glob = require 'glob' + +---@class parser.object +---@field package _visibleType? parser.visibleType + +local function getVisibleType(source) + if guide.isLiteral(source) then + return 'public' + end + if source._visibleType then + return source._visibleType + end + if source.type == 'doc.field' then + if source.visible then + source._visibleType = source.visible + return source.visible + end + end + + if source.bindDocs then + for _, doc in ipairs(source.bindDocs) do + if doc.type == 'doc.private' then + source._visibleType = 'private' + return 'private' + end + if doc.type == 'doc.protected' then + source._visibleType = 'protected' + return 'protected' + end + if doc.type == 'doc.package' then + source._visibleType = 'package' + return 'package' + end + end + end + + local fieldName = guide.getKeyName(source) + + if type(fieldName) == 'string' then + local uri = guide.getUri(source) + + local privateNames = config.get(uri, 'Lua.doc.privateName') + if #privateNames > 0 and glob.glob(privateNames)(fieldName) then + source._visibleType = 'private' + return 'private' + end + + local protectedNames = config.get(uri, 'Lua.doc.protectedName') + if #protectedNames > 0 and glob.glob(protectedNames)(fieldName) then + source._visibleType = 'protected' + return 'protected' + end + + local packageNames = config.get(uri, 'Lua.doc.packageName') + if #packageNames > 0 and glob.glob(packageNames)(fieldName) then + source._visibleType = 'package' + return 'package' + end + end + + source._visibleType = 'public' + return 'public' +end + +---@class vm.node +---@field package _visibleType parser.visibleType + +---@param source parser.object +---@return parser.visibleType +function vm.getVisibleType(source) + local node = vm.compileNode(source) + if node._visibleType then + return node._visibleType + end + for _, def in ipairs(vm.getDefs(source)) do + local visible = getVisibleType(def) + if visible ~= 'public' then + node._visibleType = visible + return visible + end + end + node._visibleType = 'public' + return 'public' +end + +---@param source parser.object +---@return vm.global? +function vm.getParentClass(source) + if source.type == 'doc.field' then + return vm.getGlobalNode(source.class) + end + if source.type == 'setfield' + or source.type == 'setindex' + or source.type == 'setmethod' + or source.type == 'tablefield' + or source.type == 'tableindex' then + return vm.getDefinedClass(guide.getUri(source), source.node) + end + return nil +end + +---@param suri uri +---@param source parser.object +---@return vm.global? +function vm.getDefinedClass(suri, source) + source = guide.getSelfNode(source) or source + local sets = vm.getVariableSets(source) + if sets then + for _, set in ipairs(sets) do + if set.bindDocs then + for _, doc in ipairs(set.bindDocs) do + if doc.type == 'doc.class' then + return vm.getGlobalNode(doc) + end + end + end + end + end + local global = vm.getGlobalNode(source) + if global then + for _, set in ipairs(global:getSets(suri)) do + if set.bindDocs then + for _, doc in ipairs(set.bindDocs) do + if doc.type == 'doc.class' then + return vm.getGlobalNode(doc) + end + end + end + end + end + return nil +end + +---@param source parser.object +---@return vm.global? +local function getEnvClass(source) + local func = guide.getParentFunction(source) + if not func or func.type ~= 'function' then + return nil + end + local parent = func.parent + if parent.type == 'setfield' + or parent.type == 'setmethod' then + local node = parent.node + return vm.getDefinedClass(guide.getUri(source), node) + end + return nil +end + +---@param parent parser.object +---@param field parser.object +function vm.isVisible(parent, field) + local visible = vm.getVisibleType(field) + if visible == 'public' then + return true + end + if visible == 'package' then + return guide.getUri(parent) == guide.getUri(field) + end + local class = vm.getParentClass(field) + if not class then + return true + end + local suri = guide.getUri(parent) + -- check .x + local myClass = vm.getDefinedClass(suri, parent) + if not myClass then + -- check function :X() ... end + myClass = getEnvClass(parent) + if not myClass then + return false + end + end + if myClass == class then + return true + end + if visible == 'protected' then + if vm.isSubType(suri, myClass, class) then + return true + end + end + return false +end diff --git a/script/vm/vm.lua b/script/vm/vm.lua index 5437b6325..4baca7628 100644 --- a/script/vm/vm.lua +++ b/script/vm/vm.lua @@ -9,8 +9,6 @@ local mathHuge = math.huge local weakMT = { __mode = 'kv' } -_ENV = nil - ---@class vm local m = {} @@ -23,6 +21,7 @@ function m.getSpecial(source) return source.special end +---@param source parser.object ---@return string? function m.getKeyName(source) if not source then diff --git a/script/workspace/loading.lua b/script/workspace/loading.lua index 66e0a3aad..5ddc4bdab 100644 --- a/script/workspace/loading.lua +++ b/script/workspace/loading.lua @@ -46,7 +46,7 @@ function mt:checkMaxPreload(uri) client.requestMessage('Info' , lang.script('MWS_MAX_PRELOAD', max) , { - lang.script + lang.script('WINDOW_INCREASE_UPPER_LIMIT'), } , function (_, index) if index == 1 then @@ -86,7 +86,7 @@ function mt:loadFile(uri, libraryUri) files.addRef(uri) end self._cache[uri] = true - log.debug(('Skip loaded file: %s'):format(uri)) + log.info(('Skip loaded file: %s'):format(uri)) else local content = pub.awaitTask('loadFile', furi.decode(uri)) self.read = self.read + 1 @@ -94,18 +94,23 @@ function mt:loadFile(uri, libraryUri) if not content then return end - log.debug(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + if files.getFile(uri) then + log.info(('Skip loaded file: %s'):format(uri)) + return + end + log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) --await.wait(function (waker) -- self._sets[#self._sets+1] = waker --end) files.setText(uri, content, false) + files.compileState(uri) if not self._cache[uri] then files.addRef(uri) end self._cache[uri] = true end if libraryUri then - log.debug('++++As library of:', libraryUri) + log.info('++++As library of:', libraryUri) end end elseif files.isDll(uri) then @@ -120,7 +125,7 @@ function mt:loadFile(uri, libraryUri) files.addRef(uri) end self._cache[uri] = true - log.debug(('Skip loaded file: %s'):format(uri)) + log.info(('Skip loaded file: %s'):format(uri)) else local content = pub.awaitTask('loadFile', furi.decode(uri)) self.read = self.read + 1 @@ -128,7 +133,11 @@ function mt:loadFile(uri, libraryUri) if not content then return end - log.debug(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) + if files.getFile(uri) then + log.info(('Skip loaded file: %s'):format(uri)) + return + end + log.info(('Preload dll at: %s , size = %.3f KB'):format(uri, #content / 1024.0)) --await.wait(function (waker) -- self._sets[#self._sets+1] = waker --end) @@ -139,7 +148,7 @@ function mt:loadFile(uri, libraryUri) self._cache[uri] = true end if libraryUri then - log.debug('++++As library of:', libraryUri) + log.info('++++As library of:', libraryUri) end end end @@ -147,9 +156,9 @@ function mt:loadFile(uri, libraryUri) end ---@async -function mt:loadAll() +function mt:loadAll(fileName) local startClock = os.clock() - log.info('Load files from disk:', self.scp:getName()) + log.info('Load files from disk:', fileName) while self.read < self.max do self:update() local loader = table.remove(self._stash) @@ -161,7 +170,7 @@ function mt:loadAll() end end local loadedClock = os.clock() - log.info(('Loaded files takes [%.3f] sec: %s'):format(loadedClock - startClock, self.scp:getName())) + log.info(('Loaded files takes [%.3f] sec: %s'):format(loadedClock - startClock, fileName)) self._bar:remove() self._bar = progress.create(self.scp.uri, lang.script('WORKSPACE_LOADING', self.scp.uri), 0) for i, set in ipairs(self._sets) do @@ -170,8 +179,8 @@ function mt:loadAll() self.read = i self:update() end - log.info(('Compile files takes [%.3f] sec: %s'):format(os.clock() - loadedClock, self.scp:getName())) - log.info('Loaded finish:', self.scp:getName()) + log.info(('Compile files takes [%.3f] sec: %s'):format(os.clock() - loadedClock, fileName)) + log.info('Loaded finish:', fileName) end function mt:remove() diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 37ecdc473..b6768ef66 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -13,6 +13,7 @@ local m = {} ---@field scp scope ---@field nameMap table ---@field visibleCache table +---@field requireCache table local mt = {} mt.__index = mt @@ -25,6 +26,7 @@ local function createRequireManager(scp) scp = scp, nameMap = {}, visibleCache = {}, + requireCache = {}, }, mt) end @@ -55,13 +57,26 @@ end ---@param path string ---@return require-manager.visibleResult[] function mt:getRequireResultByPath(path) + local vm = require 'vm' local uri = furi.encode(path) + local result = {} + if vm.isMetaFile(uri) then + local metaName = vm.getMetaName(uri) + if metaName then + if vm.isMetaFileRequireable(uri) then + result[#result+1] = { + name = metaName, + searcher = '[[meta]]', + } + end + return result + end + end local searchers = config.get(self.scp.uri, 'Lua.runtime.path') local strict = config.get(self.scp.uri, 'Lua.runtime.pathStrict') local separator = config.get(self.scp.uri, 'Lua.completion.requireSeparator') local libUri = files.getLibraryUri(self.scp.uri, uri) local libraryPath = libUri and furi.decode(libUri) - local result = {} for _, searcher in ipairs(searchers) do local isAbsolute = searcher:match '^%a+%:' local startsWithSlash = searcher:match '^[/\\]' @@ -107,7 +122,7 @@ function mt:getRequireResultByPath(path) cutedPath = currentPath:sub(pos) head = currentPath:sub(1, pos - 1) pos = currentPath:match('[/\\]+()', pos) - if platform.OS == 'Windows' then + if platform.os == 'windows' then searcher = searcher :gsub('[/\\]+', '\\') else searcher = searcher :gsub('[/\\]+', '/') @@ -145,7 +160,7 @@ function mt:getVisiblePath(path) and not self.scp:isLinkedUri(uri) then return {} end - path = workspace.normalize(path) + path = files.normalize(path) local result = self.visibleCache[path] if not result then result = self:getRequireResultByPath(path) @@ -155,20 +170,31 @@ function mt:getVisiblePath(path) end --- ๆŸฅๆ‰พ็ฌฆๅˆๆŒ‡ๅฎšrequire name็š„ๆ‰€ๆœ‰uri ----@param suri uri ---@param name string ---@return uri[] ---@return table? -function mt:findUrisByRequireName(suri, name) - if type(name) ~= 'string' then - return {} - end +function mt:searchUrisByRequireName(name) + local vm = require 'vm' local searchers = config.get(self.scp.uri, 'Lua.runtime.path') local strict = config.get(self.scp.uri, 'Lua.runtime.pathStrict') local separator = config.get(self.scp.uri, 'Lua.completion.requireSeparator') local path = name:gsub('%' .. separator, '/'):lower() local results = {} local searcherMap = {} + local excludes = {} + + for uri in files.eachFile(self.scp.uri) do + if vm.isMetaFileRequireable(uri) then + local metaName = vm.getMetaName(uri) + if metaName == name then + results[#results+1] = uri + return results + end + if metaName then + excludes[uri] = true + end + end + end for _, searcher in ipairs(searchers) do local startsWithSlash = searcher:match '^[/\\]' @@ -194,12 +220,7 @@ function mt:findUrisByRequireName(suri, name) and suri ~= uri and util.stringEndWith(uri:lower(), tail) then results[#results+1] = uri - local parentUri = files.getLibraryUri(self.scp.uri, uri) or self.scp.uri - if parentUri == nil or parentUri == '' then - parentUri = furi.encode '' - end - local relative = uri:sub(#parentUri + 1):sub(1, - #tail) - searcherMap[uri] = workspace.normalize(relative .. searcher) + searcherMap[uri] = files.normalize(relative .. searcher) end end end @@ -217,6 +238,35 @@ function mt:findUrisByRequireName(suri, name) return results, searcherMap end +--- ๆŸฅๆ‰พ็ฌฆๅˆๆŒ‡ๅฎšrequire name็š„ๆ‰€ๆœ‰uri๏ผŒๅนถๆŽ’้™คๅฝ“ๅ‰ๆ–‡ไปถ +---@param suri uri +---@param name string +---@return uri[] +---@return table? +function mt:findUrisByRequireName(suri, name) + if type(name) ~= 'string' then + return {} + end + local cache = self.requireCache[name] + if not cache then + local results, searcherMap = self:searchUrisByRequireName(name) + cache = { + results = results, + searcherMap = searcherMap, + } + self.requireCache[name] = cache + end + local results = {} + local searcherMap = {} + for _, uri in ipairs(cache.results) do + if uri ~= suri then + results[#results+1] = uri + searcherMap[uri] = cache.searcherMap and cache.searcherMap[uri] + end + end + return results, searcherMap +end + ---@param uri uri ---@param path string ---@return require-manager.visibleResult[] @@ -230,6 +280,8 @@ end ---@param uri uri ---@param name string +---@return uri[] +---@return table? function m.findUrisByRequireName(uri, name) local scp = scope.getScope(uri) ---@type require-manager @@ -238,13 +290,41 @@ function m.findUrisByRequireName(uri, name) return mgr:findUrisByRequireName(uri, name) end -files.watch(function (ev, uri) - if ev == 'create' or ev == 'delete' then - for _, scp in ipairs(workspace.folders) do - scp:set('requireManager', nil) +---@param suri uri +---@param uri uri +---@param name string +---@return boolean +function m.isMatchedUri(suri, uri, name) + local searchers = config.get(suri, 'Lua.runtime.path') + local strict = config.get(suri, 'Lua.runtime.pathStrict') + local separator = config.get(suri, 'Lua.completion.requireSeparator') + local path = name:gsub('%' .. separator, '/') + + for _, searcher in ipairs(searchers) do + local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%'))) + fspath = files.normalize(fspath) + local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '') + if util.stringEndWith(uri, tail) then + local parentUri = files.getLibraryUri(suri, uri) or uri + if parentUri == nil or parentUri == '' then + parentUri = furi.encode '/' + end + local relative = uri:sub(#parentUri + 1):sub(1, - #tail) + if not strict + or relative == '/' + or relative == '' then + return true + end end - scope.fallback:set('requireManager', nil) end + return false +end + +files.watch(function (ev, uri) + for _, scp in ipairs(workspace.folders) do + scp:set('requireManager', nil) + end + scope.fallback:set('requireManager', nil) end) config.watch(function (uri, key, value, oldValue) diff --git a/script/workspace/scope.lua b/script/workspace/scope.lua index 4649d3548..cfdfdc909 100644 --- a/script/workspace/scope.lua +++ b/script/workspace/scope.lua @@ -37,6 +37,12 @@ function mt:removeAllLinks() self._links = {} end +---@return fun(): uri +---@return table +function mt:eachLink() + return next, self._links +end + ---@param uri uri ---@return boolean function mt:isChildUri(uri) @@ -46,7 +52,17 @@ function mt:isChildUri(uri) if not self.uri then return false end - return uri:sub(1, #self.uri) == self.uri + if self.uri == uri then + return true + end + if uri:sub(1, #self.uri) ~= self.uri then + return false + end + if uri:sub(#self.uri, #self.uri) == '/' + or uri:sub(#self.uri + 1, #self.uri + 1) == '/' then + return true + end + return false end ---@param uri uri @@ -56,9 +72,17 @@ function mt:isLinkedUri(uri) return false end for linkUri in pairs(self._links) do - if uri:sub(1, #linkUri) == linkUri then + if uri == linkUri then + return true + end + if uri:sub(1, #linkUri) ~= linkUri then + goto CONTINUE + end + if uri:sub(#linkUri, #linkUri) == '/' + or uri:sub(#linkUri + 1, #linkUri + 1) == '/' then return true end + ::CONTINUE:: end return false end @@ -101,8 +125,6 @@ function mt:set(k, v) return v end ----@param k string ----@return any function mt:get(k) return self._data[k] end @@ -213,11 +235,11 @@ function m.getLinkedScope(uri) return nil end ----@param uri uri +---@param uri? uri ---@return scope function m.getScope(uri) - return m.getFolder(uri) - or m.getLinkedScope(uri) + return uri and (m.getFolder(uri) + or m.getLinkedScope(uri)) or m.fallback end diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index 016c3867e..e31337a06 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -45,17 +45,16 @@ end --- ๅˆๅง‹ๅŒ–ๅทฅไฝœๅŒบ function m.create(uri) - if furi.isValid(uri) then - uri = furi.normalize(uri) - end log.info('Workspace create: ', uri) + local scp = scope.createFolder(uri) + m.folders[#m.folders+1] = scp if uri == furi.encode '/' or uri == furi.encode(os.getenv 'HOME' or '') then - client.showMessage('Error', lang.script('WORKSPACE_NOT_ALLOWED', furi.decode(uri))) - return + if not FORCE_ACCEPT_WORKSPACE then + client.showMessage('Error', lang.script('WORKSPACE_NOT_ALLOWED', furi.decode(uri))) + scp:set('bad root', true) + end end - local scp = scope.createFolder(uri) - m.folders[#m.folders+1] = scp end function m.remove(uri) @@ -87,27 +86,45 @@ function m.getRootUri(uri) end local globInteferFace = { - type = function (path) + type = function (path, data) + if data[path] then + return data[path] + end local result pcall(function () - local status = fs.symlink_status(path):type() - if status == 'directory' then + if fs.is_directory(fs.path(path)) then result = 'directory' - elseif status == 'regular' then + data[path] = 'directory' + else result = 'file' + data[path] = 'file' end end) return result end, - list = function (path) + list = function (path, data) + if data[path] == 'file' then + return nil + end local fullPath = fs.path(path) - if fs.symlink_status(fullPath):type() ~= 'directory' then + if not fs.is_directory(fullPath) then + data[path] = 'file' return nil end + data[path] = true local paths = {} pcall(function () - for fullpath in fs.pairs(fullPath) do - paths[#paths+1] = fullpath:string() + for fullpath, status in fs.pairs(fullPath) do + local pathString = fullpath:string() + paths[#paths+1] = pathString + local st = status:type() + if st == 'directory' + or st == 'symlink' + or st == 'junction' then + data[pathString] = 'directory' + else + data[pathString] = 'file' + end end end) return paths @@ -171,7 +188,7 @@ function m.getNativeMatcher(scp) local matcher = glob.gitignore(pattern, { root = scp.uri and furi.decode(scp.uri), - ignoreCase = platform.OS == 'Windows', + ignoreCase = platform.os == 'windows', }, globInteferFace) scp:set('nativeMatcher', matcher) @@ -202,12 +219,15 @@ function m.getLibraryMatchers(scp) for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.library')) do path = m.getAbsolutePath(scp.uri, path) if path then - librarys[m.normalize(path)] = true + librarys[files.normalize(path)] = true end end - log.debug('meta path:', scp:get 'metaPath') - if scp:get 'metaPath' then - librarys[m.normalize(scp:get 'metaPath')] = true + local metaPaths = scp:get 'metaPaths' + log.debug('meta path:', inspect(metaPaths)) + if metaPaths then + for _, metaPath in ipairs(metaPaths) do + librarys[files.normalize(metaPath)] = true + end end local matchers = {} @@ -216,7 +236,7 @@ function m.getLibraryMatchers(scp) local nPath = fs.absolute(fs.path(path)):string() local matcher = glob.gitignore(pattern, { root = path, - ignoreCase = platform.OS == 'Windows', + ignoreCase = platform.os == 'windows', }, globInteferFace) matchers[#matchers+1] = { uri = furi.encode(nPath), @@ -226,13 +246,12 @@ function m.getLibraryMatchers(scp) end scp:set('libraryMatcher', matchers) - log.debug('library matcher:', inspect(matchers)) + --log.debug('library matcher:', inspect(matchers)) return matchers end --- ๆ–‡ไปถๆ˜ฏๅฆ่ขซๅฟฝ็•ฅ ----@async ---@param uri uri function m.isIgnored(uri) local scp = scope.getScope(uri) @@ -269,7 +288,7 @@ function m.awaitLoadFile(uri) scp:get('cachedUris')[uri] = true ld:loadFile(uri) end) - ld:loadAll() + ld:loadAll(uri) end function m.removeFile(uri) @@ -307,26 +326,39 @@ function m.awaitPreload(scp) local native = m.getNativeMatcher(scp) local librarys = m.getLibraryMatchers(scp) - if scp.uri then + if scp.uri and not scp:get('bad root') then log.info('Scan files at:', scp:getName()) + scp:gc(fw.watch(files.normalize(furi.decode(scp.uri)), true, function (path) + local rpath = m.getRelativePath(path) + if native(rpath) then + return false + end + return true + end)) local count = 0 ---@async native:scan(furi.decode(scp.uri), function (path) local uri = files.getRealUri(furi.encode(path)) scp:get('cachedUris')[uri] = true ld:loadFile(uri) - end, function () ---@async + end, function (_) ---@async count = count + 1 if count == 100000 then client.showMessage('Warning', lang.script('WORKSPACE_SCAN_TOO_MUCH', count, furi.decode(scp.uri))) end end) - scp:gc(fw.watch(m.normalize(furi.decode(scp.uri)))) end for _, libMatcher in ipairs(librarys) do log.info('Scan library at:', libMatcher.uri) local count = 0 + scp:gc(fw.watch(furi.decode(libMatcher.uri), true, function (path) + local rpath = m.getRelativePath(path) + if libMatcher.matcher(rpath) then + return false + end + return true + end)) scp:addLink(libMatcher.uri) ---@async libMatcher.matcher:scan(furi.decode(libMatcher.uri), function (path) @@ -339,14 +371,13 @@ function m.awaitPreload(scp) client.showMessage('Warning', lang.script('WORKSPACE_SCAN_TOO_MUCH', count, furi.decode(libMatcher.uri))) end end) - scp:gc(fw.watch(furi.decode(libMatcher.uri))) end -- must wait for other scopes to add library await.sleep(0.1) log.info(('Found %d files at:'):format(ld.max), scp:getName()) - ld:loadAll() + ld:loadAll(scp:getName()) log.info('Preload finish at:', scp:getName()) end @@ -372,52 +403,18 @@ function m.findUrisByFilePath(path) return results end ----@param path string ----@return string -function m.normalize(path) - path = path:gsub('%$%{(.-)%}', function (key) - if key == '3rd' then - return (ROOT / 'meta' / '3rd'):string() - end - if key:sub(1, 4) == 'env:' then - local env = os.getenv(key:sub(5)) - return env - end - end) - path = util.expandPath(path) - path = path:gsub('^%.[/\\]+', '') - for _ = 1, 1000 do - if path:sub(1, 2) == '..' then - break - end - local count - path, count = path:gsub('[^/\\]+[/\\]+%.%.', '', 1) - if count == 0 then - break - end - end - if platform.OS == 'Windows' then - path = path:gsub('[/\\]+', '\\') - :gsub('[/\\]+$', '') - :gsub('^(%a:)$', '%1\\') - else - path = path:gsub('[/\\]+', '/') - :gsub('[/\\]+$', '') - end - return path -end ---@param folderUri? uri ---@param path string ---@return string? function m.getAbsolutePath(folderUri, path) - path = m.normalize(path) + path = files.normalize(path) if fs.path(path):is_relative() then if not folderUri then return nil end local folderPath = furi.decode(folderUri) - path = m.normalize(folderPath .. '/' .. path) + path = files.normalize(folderPath .. '/' .. path) end return path end @@ -437,12 +434,14 @@ function m.getRelativePath(uriOrPath, startWithSlash) end local scp = scope.getScope(uri) if not scp.uri then - local relative = m.normalize(path) + local relative = files.normalize(path) return relative:gsub('^[/\\]+', ''), false end - local _, pos = m.normalize(path):find(furi.decode(scp.uri), 1, true) + local _, pos = files.normalize(path):find(furi.decode(scp.uri), 1, true) if pos then - path = path:sub(pos + 1) + return files.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true + else + return files.normalize(path):gsub('^[/\\]+', ''), false end path = m.normalize(path) if not startWithSlash then @@ -478,10 +477,6 @@ function m.flushFiles(scp) for uri in pairs(cachedUris) do files.delRef(uri) end - collectgarbage() - collectgarbage() - -- TODO: wait maillist - collectgarbage 'restart' end ---@param scp scope @@ -502,6 +497,8 @@ end ---@async ---@param scp scope function m.awaitReload(scp) + await.unique('workspace reload:' .. scp:getName()) + await.sleep(0.1) scp:set('ready', false) scp:set('nativeMatcher', nil) scp:set('libraryMatcher', nil) @@ -541,11 +538,26 @@ function m.awaitReady(uri) end ---@param uri uri +---@return boolean function m.isReady(uri) local scp = scope.getScope(uri) return scp:get('ready') == true end +---@return boolean +function m.isAllReady() + local scp = scope.fallback + if not scp:get 'ready' then + return false + end + for _, folder in ipairs(scope.folders) do + if not folder:get 'ready' then + return false + end + end + return true +end + function m.getLoadingProcess(uri) local scp = scope.getScope(uri) ---@type workspace.loading @@ -563,7 +575,9 @@ config.watch(function (uri, key, value, oldValue) or key:find '^Lua.type' or key:find '^files' then if value ~= oldValue then - m.reload(scope.getScope(uri)) + local scp = scope.getScope(uri) + m.reload(scp) + m.resetFiles(scp) end end end) diff --git a/test.lua b/test.lua index 52a525ac7..1036b8168 100644 --- a/test.lua +++ b/test.lua @@ -13,6 +13,7 @@ METAPATH = METAPATH or (ROOT:string() .. '/meta') collectgarbage 'generational' +---@diagnostic disable-next-line: duplicate-set-field io.write = function () end ---@diagnostic disable-next-line: lowercase-global @@ -37,8 +38,8 @@ local function test(name) local clock = os.clock() print(('ๆต‹่ฏ•[%s]...'):format(name)) local originRequire = require - require = function (n, ...) - local v, p = originRequire(n, ...) + require = function (n) + local v, p = originRequire(n) if p and p:find 'test/' then package.loaded[n] = nil end @@ -56,8 +57,8 @@ local function testAll() test 'references' test 'hover' test 'completion' - test 'crossfile' test 'diagnostics' + test 'crossfile' test 'highlight' test 'rename' test 'signature' @@ -65,7 +66,7 @@ local function testAll() test 'document_symbol' test 'code_action' test 'type_formatting' - --test 'other' + test 'other' end local files = require "files" @@ -76,17 +77,24 @@ local function main() local lclient = require 'lclient' local ws = require 'workspace' + local furi = require 'file-uri' + require 'vm' --log.print = true + TESTROOT = ROOT:string() .. '/test_root/' + TESTROOTURI = furi.encode(TESTROOT) + TESTURI = furi.encode(TESTROOT .. 'unittest.lua') + ---@async lclient():start(function (client) client:registerFakers() + local rootUri = furi.encode(TESTROOT) client:initialize { - rootUri = '', + rootUri = rootUri, } - ws.awaitReady() + ws.awaitReady(rootUri) print('Loaded files in', os) for uri in files.eachFile() do @@ -99,6 +107,8 @@ local function main() test 'tclient' test 'full' + test 'plugins.test' + test 'cli.test' end loadAllLibs() diff --git a/test/catch.lua b/test/catch.lua index 01aac6653..b66d3e636 100644 --- a/test/catch.lua +++ b/test/catch.lua @@ -1,5 +1,7 @@ local m = require 'lpeglabel' +---@class catched +---@operator add: catched local mt = {} local function catchedTable() @@ -35,6 +37,8 @@ end ---@param script string ---@param seps string +---@return string +---@return table return function (script, seps) local tokens = parseTokens(script, seps) local newBuf = {} diff --git a/test/cli/test.lua b/test/cli/test.lua new file mode 100644 index 000000000..7b988f68a --- /dev/null +++ b/test/cli/test.lua @@ -0,0 +1 @@ +require 'cli.visualize.test' diff --git a/test/cli/visualize/test.lua b/test/cli/visualize/test.lua new file mode 100644 index 000000000..c9722c32b --- /dev/null +++ b/test/cli/visualize/test.lua @@ -0,0 +1,23 @@ +local visualize = require 'cli.visualize' + +local testDataDir = 'test/cli/visualize/testdata/' + +local function TestVisualize(fileName) + local inputFile = testDataDir .. fileName .. '.txt' + local outputFile = testDataDir .. fileName .. '-expected.txt' + local output = '' + local writer = {} + function writer:write(text) + output = output .. text + end + visualize.visualizeAst(io.open(inputFile):read('a'), writer) + local expectedOutput = io.open(outputFile):read('a') + if expectedOutput ~= output then + -- uncomment this to update reference output + --io.open(outputFile, "w+"):write(output):close() + error('output mismatch for test file ' .. inputFile) + end +end + +TestVisualize('all-types') +TestVisualize('shorten-names') diff --git a/test/cli/visualize/testdata/all-types-expected.txt b/test/cli/visualize/testdata/all-types-expected.txt new file mode 100644 index 000000000..5391658ce --- /dev/null +++ b/test/cli/visualize/testdata/all-types-expected.txt @@ -0,0 +1,587 @@ +digraph AST { + node [shape = rect] + "main:0:300000" [ + label="main\l\l" + tooltip="start: 0\nfinish: 300000\nlocals:
\nstate:
\nreturns:
\n1: \n2: \n3: \n4: \n5: \n6: \n7: \n8: \n9: \n10: \n" + ] + "setglobal:0:3" [ + label="setglobal\lfoo\l" + tooltip="start: 0\nfinish: 3\nrange: 35\nvalue: \nnode: \n1: foo\n" + ] + "main:0:300000" -> "setglobal:0:3" + "table:6:35" [ + label="table\l\l" + tooltip="start: 6\nfinish: 35\n1: \n2: \n3: \n" + ] + "setglobal:0:3" -> "table:6:35" + "tablefield:7:8" [ + label="tablefield\lx\l" + tooltip="start: 7\nfinish: 8\nnode: \nrange: 12\nfield: \nvalue: \n" + ] + "table:6:35" -> "tablefield:7:8" + "field:7:8" [ + label="field\lx\l" + tooltip="start: 7\nfinish: 8\n1: x\n" + ] + "tablefield:7:8" -> "field:7:8" + "integer:11:12" [ + label="integer\l5\l" + tooltip="start: 11\nfinish: 12\n1: 5\n" + ] + "tablefield:7:8" -> "integer:11:12" + "tablefield:14:17" [ + label="tablefield\lbar\l" + tooltip="start: 14\nfinish: 17\nnode: \nrange: 21\nfield: \nvalue: \n" + ] + "table:6:35" -> "tablefield:14:17" + "field:14:17" [ + label="field\lbar\l" + tooltip="start: 14\nfinish: 17\n1: bar\n" + ] + "tablefield:14:17" -> "field:14:17" + "integer:20:21" [ + label="integer\l6\l" + tooltip="start: 20\nfinish: 21\n1: 6\n" + ] + "tablefield:14:17" -> "integer:20:21" + "tableindex:23:30" [ + label="tableindex\lbaz\l" + tooltip="start: 23\nfinish: 30\nrange: 34\nnode: \nvalue: \nindex: \n" + ] + "table:6:35" -> "tableindex:23:30" + "string:24:29" [ + label="string\lbaz\l" + tooltip="start: 24\nfinish: 29\n1: baz\n2: \"\n" + ] + "tableindex:23:30" -> "string:24:29" + "integer:33:34" [ + label="integer\l7\l" + tooltip="start: 33\nfinish: 34\n1: 7\n" + ] + "tableindex:23:30" -> "integer:33:34" + "setfield:10000:10005" [ + label="setfield\ly\l" + tooltip="start: 10000\nfinish: 10005\nrange: 10017\nfield: \nnode: \ndot: \nvalue: \n" + ] + "main:0:300000" -> "setfield:10000:10005" + "getglobal:10000:10003" [ + label="getglobal\lfoo\l" + tooltip="start: 10000\nfinish: 10003\nnext: \nnode: \n1: foo\n" + ] + "setfield:10000:10005" -> "getglobal:10000:10003" + "field:10004:10005" [ + label="field\ly\l" + tooltip="start: 10004\nfinish: 10005\n1: y\n" + ] + "setfield:10000:10005" -> "field:10004:10005" + "binary:10008:10017" [ + label="binary\l+\l" + tooltip="start: 10008\nfinish: 10017\nop: \n1: \n2: \n" + ] + "setfield:10000:10005" -> "binary:10008:10017" + "getfield:10008:10013" [ + label="getfield\lx\l" + tooltip="start: 10008\nfinish: 10013\nfield: \ndot: \nnode: \n" + ] + "binary:10008:10017" -> "getfield:10008:10013" + "getglobal:10008:10011" [ + label="getglobal\lfoo\l" + tooltip="start: 10008\nfinish: 10011\nnext: \nnode: \n1: foo\n" + ] + "getfield:10008:10013" -> "getglobal:10008:10011" + "field:10012:10013" [ + label="field\lx\l" + tooltip="start: 10012\nfinish: 10013\n1: x\n" + ] + "getfield:10008:10013" -> "field:10012:10013" + "integer:10016:10017" [ + label="integer\l1\l" + tooltip="start: 10016\nfinish: 10017\n1: 1\n" + ] + "binary:10008:10017" -> "integer:10016:10017" + "setindex:20000:20006" [ + label="setindex\l1\l" + tooltip="start: 20000\nfinish: 20006\nrange: 20015\nnode: \nvalue: \nindex: \n" + ] + "main:0:300000" -> "setindex:20000:20006" + "getglobal:20000:20003" [ + label="getglobal\lfoo\l" + tooltip="start: 20000\nfinish: 20003\nnext: \nnode: \n1: foo\n" + ] + "setindex:20000:20006" -> "getglobal:20000:20003" + "integer:20004:20005" [ + label="integer\l1\l" + tooltip="start: 20004\nfinish: 20005\n1: 1\n" + ] + "setindex:20000:20006" -> "integer:20004:20005" + "getindex:20009:20015" [ + label="getindex\l0\l" + tooltip="start: 20009\nfinish: 20015\nnode: \nindex: \n" + ] + "setindex:20000:20006" -> "getindex:20009:20015" + "getglobal:20009:20012" [ + label="getglobal\lfoo\l" + tooltip="start: 20009\nfinish: 20012\nnext: \nnode: \n1: foo\n" + ] + "getindex:20009:20015" -> "getglobal:20009:20012" + "integer:20013:20014" [ + label="integer\l0\l" + tooltip="start: 20013\nfinish: 20014\n1: 0\n" + ] + "getindex:20009:20015" -> "integer:20013:20014" + "setmethod:30009:30023" [ + label="setmethod\lsomeMethod\l" + tooltip="start: 30009\nfinish: 30023\nrange: 30042\nmethod: \nvalue: \ncolon: \nvstart: 30000\nnode: \n" + ] + "main:0:300000" -> "setmethod:30009:30023" + "getglobal:30009:30012" [ + label="getglobal\lfoo\l" + tooltip="start: 30009\nfinish: 30012\nnode: \nnext: \n1: foo\n" + ] + "setmethod:30009:30023" -> "getglobal:30009:30012" + "method:30013:30023" [ + label="method\lsomeMethod\l" + tooltip="start: 30013\nfinish: 30023\n1: someMethod\n" + ] + "setmethod:30009:30023" -> "method:30013:30023" + "function:30000:30042" [ + label="function\l\l" + tooltip="start: 30000\nfinish: 30042\nhasReturn: true\nargs: \nlocals:
\nkeyword:
\nbstart: 30025\nreturns:
\n1: \n" + ] + "setmethod:30009:30023" -> "function:30000:30042" + "funcargs:30023:30025" [ + label="funcargs\l\l" + tooltip="start: 30023\nfinish: 30025\n1: \n" + ] + "function:30000:30042" -> "funcargs:30023:30025" + "self:30008:30008" [ + label="self\lself\l" + tooltip="start: 30008\nfinish: 30008\neffect: 30008\n1: self\n" + ] + "funcargs:30023:30025" -> "self:30008:30008" + "return:30026:30038" [ + label="return\l\l" + tooltip="start: 30026\nfinish: 30038\n1: \n" + ] + "function:30000:30042" -> "return:30026:30038" + "boolean:30033:30038" [ + label="boolean\l\l" + tooltip="start: 30033\nfinish: 30038\n1: false\n" + ] + "return:30026:30038" -> "boolean:30033:30038" + "local:40006:40007" [ + label="local\ls\l" + tooltip="start: 40006\nfinish: 40007\nrange: 40011\nlocPos: 40000\nvalue: \nref:
\neffect: 40011\n1: s\n" + ] + "main:0:300000" -> "local:40006:40007" + "integer:40010:40011" [ + label="integer\l0\l" + tooltip="start: 40010\nfinish: 40011\n1: 0\n" + ] + "local:40006:40007" -> "integer:40010:40011" + "while:50000:70003" [ + label="while\l\l" + tooltip="start: 50000\nfinish: 70003\nfilter: \nkeyword:
\nbstart: 50013\n1: \n" + ] + "main:0:300000" -> "while:50000:70003" + "binary:50006:50012" [ + label="binary\l<\l" + tooltip="start: 50006\nfinish: 50012\nop: \n1: \n2: \n" + ] + "while:50000:70003" -> "binary:50006:50012" + "getlocal:50006:50007" [ + label="getlocal\ls\l" + tooltip="start: 50006\nfinish: 50007\nnode: \n1: s\n" + ] + "binary:50006:50012" -> "getlocal:50006:50007" + "integer:50010:50012" [ + label="integer\l10\l" + tooltip="start: 50010\nfinish: 50012\n1: 10\n" + ] + "binary:50006:50012" -> "integer:50010:50012" + "setlocal:60001:60002" [ + label="setlocal\ls\l" + tooltip="start: 60001\nfinish: 60002\nrange: 60038\nvalue: \nnode: \n1: s\n" + ] + "while:50000:70003" -> "setlocal:60001:60002" + "binary:60005:60038" [ + label="binary\l+\l" + tooltip="start: 60005\nfinish: 60038\nop: \n1: \n2: \n" + ] + "setlocal:60001:60002" -> "binary:60005:60038" + "getlocal:60005:60006" [ + label="getlocal\ls\l" + tooltip="start: 60005\nfinish: 60006\nnode: \n1: s\n" + ] + "binary:60005:60038" -> "getlocal:60005:60006" + "paren:60009:60038" [ + label="paren\l\l" + tooltip="start: 60009\nfinish: 60038\nexp: \n" + ] + "binary:60005:60038" -> "paren:60009:60038" + "binary:60010:60037" [ + label="binary\lor\l" + tooltip="start: 60010\nfinish: 60037\nop: \n1: \n2: \n" + ] + "paren:60009:60038" -> "binary:60010:60037" + "call:60010:60031" [ + label="call\l\l" + tooltip="start: 60010\nfinish: 60031\nargs: \nnode: \n" + ] + "binary:60010:60037" -> "call:60010:60031" + "getmethod:60010:60024" [ + label="getmethod\lsomeMethod\l" + tooltip="start: 60010\nfinish: 60024\nmethod: \nnode: \ncolon: \n" + ] + "call:60010:60031" -> "getmethod:60010:60024" + "getglobal:60010:60013" [ + label="getglobal\lfoo\l" + tooltip="start: 60010\nfinish: 60013\nnext: \nnode: \n1: foo\n" + ] + "getmethod:60010:60024" -> "getglobal:60010:60013" + "method:60014:60024" [ + label="method\lsomeMethod\l" + tooltip="start: 60014\nfinish: 60024\n1: someMethod\n" + ] + "getmethod:60010:60024" -> "method:60014:60024" + "callargs:60024:60031" [ + label="callargs\l\l" + tooltip="start: 60024\nfinish: 60031\n1: \n2: \n3: \n" + ] + "call:60010:60031" -> "callargs:60024:60031" + "self:60013:60014" [ + label="self\lself\l" + tooltip="start: 60013\nfinish: 60014\n1: self\n" + ] + "callargs:60024:60031" -> "self:60013:60014" + "getglobal:60025:60026" [ + label="getglobal\li\l" + tooltip="start: 60025\nfinish: 60026\nnode: \n1: i\n" + ] + "callargs:60024:60031" -> "getglobal:60025:60026" + "integer:60028:60030" [ + label="integer\l10\l" + tooltip="start: 60028\nfinish: 60030\n1: 10\n" + ] + "callargs:60024:60031" -> "integer:60028:60030" + "integer:60035:60037" [ + label="integer\l10\l" + tooltip="start: 60035\nfinish: 60037\n1: 10\n" + ] + "binary:60010:60037" -> "integer:60035:60037" + "call:80000:80008" [ + label="call\l\l" + tooltip="start: 80000\nfinish: 80008\nargs: \nnode: \n" + ] + "main:0:300000" -> "call:80000:80008" + "getglobal:80000:80005" [ + label="getglobal\lprint\l" + tooltip="start: 80000\nfinish: 80005\nnode: \n1: print\n" + ] + "call:80000:80008" -> "getglobal:80000:80005" + "callargs:80005:80008" [ + label="callargs\l\l" + tooltip="start: 80005\nfinish: 80008\n1: \n" + ] + "call:80000:80008" -> "callargs:80005:80008" + "getlocal:80006:80007" [ + label="getlocal\ls\l" + tooltip="start: 80006\nfinish: 80007\nnode: \n1: s\n" + ] + "callargs:80005:80008" -> "getlocal:80006:80007" + "loop:90000:140003" [ + label="loop\l\l" + tooltip="start: 90000\nfinish: 140003\ninit: \nstep: \nmax: \nlocals:
\nkeyword:
\nbstart: 90018\nloc: \n1: \n" + ] + "main:0:300000" -> "loop:90000:140003" + "local:90004:90005" [ + label="local\lj\l" + tooltip="start: 90004\nfinish: 90005\neffect: 90017\n1: j\n" + ] + "loop:90000:140003" -> "local:90004:90005" + "integer:90008:90010" [ + label="integer\l10\l" + tooltip="start: 90008\nfinish: 90010\n1: 10\n" + ] + "loop:90000:140003" -> "integer:90008:90010" + "integer:90012:90013" [ + label="integer\l1\l" + tooltip="start: 90012\nfinish: 90013\n1: 1\n" + ] + "loop:90000:140003" -> "integer:90012:90013" + "integer:90015:90017" [ + label="integer\l-1\l" + tooltip="start: 90015\nfinish: 90017\n1: -1\n" + ] + "loop:90000:140003" -> "integer:90015:90017" + "in:100001:130004" [ + label="in\l\l" + tooltip="start: 100001\nfinish: 130004\nexps: \nlabels:
\nlocals:
\nkeyword:
\nbstart: 100020\nkeys: \n1: \n2: \n" + ] + "loop:90000:140003" -> "in:100001:130004" + "list:100005:100006" [ + label="list\l\l" + tooltip="start: 100005\nfinish: 100006\nrange: 100009\n1: \n" + ] + "in:100001:130004" -> "list:100005:100006" + "local:100005:100006" [ + label="local\li\l" + tooltip="start: 100005\nfinish: 100006\neffect: 100019\n1: i\n" + ] + "list:100005:100006" -> "local:100005:100006" + "list:100010:100019" [ + label="list\l\l" + tooltip="start: 100010\nfinish: 100019\n1: \n" + ] + "in:100001:130004" -> "list:100010:100019" + "call:100010:100019" [ + label="call\l\l" + tooltip="start: 100010\nfinish: 100019\nargs: \nnode: \n" + ] + "list:100010:100019" -> "call:100010:100019" + "getglobal:100010:100016" [ + label="getglobal\lipairs\l" + tooltip="start: 100010\nfinish: 100016\nspecial: ipairs\nnode: \n1: ipairs\n" + ] + "call:100010:100019" -> "getglobal:100010:100016" + "callargs:100016:100019" [ + label="callargs\l\l" + tooltip="start: 100016\nfinish: 100019\n1: \n" + ] + "call:100010:100019" -> "callargs:100016:100019" + "getlocal:100017:100018" [ + label="getlocal\ls\l" + tooltip="start: 100017\nfinish: 100018\nnode: \n1: s\n" + ] + "callargs:100016:100019" -> "getlocal:100017:100018" + "goto:110007:110015" [ + label="goto\lfoolabel\l" + tooltip="start: 110007\nfinish: 110015\nkeyStart: 110002\nnode: \n1: foolabel\n" + ] + "in:100001:130004" -> "goto:110007:110015" + "label:120004:120012" [ + label="label\lfoolabel\l" + tooltip="start: 120004\nfinish: 120012\nref:
\n1: foolabel\n" + ] + "in:100001:130004" -> "label:120004:120012" + "setglobal:160009:160012" [ + label="setglobal\lfoo\l" + tooltip="start: 160009\nfinish: 160012\nrange: 210003\nvstart: 160000\nvalue: \nnode: \n1: foo\n" + ] + "main:0:300000" -> "setglobal:160009:160012" + "function:160000:210003" [ + label="function\l\l" + tooltip="start: 160000\nfinish: 210003\nkeyword:
\nhasReturn: true\nargs: \nbstart: 160014\nreturns:
\n1: \n" + ] + "setglobal:160009:160012" -> "function:160000:210003" + "funcargs:160012:160014" [ + label="funcargs\l\l" + tooltip="start: 160012\nfinish: 160014\n" + ] + "function:160000:210003" -> "funcargs:160012:160014" + "return:170001:200004" [ + label="return\l\l" + tooltip="start: 170001\nfinish: 200004\n1: \n" + ] + "function:160000:210003" -> "return:170001:200004" + "function:170008:200004" [ + label="function\l\l" + tooltip="start: 170008\nfinish: 200004\nargs: \nlocals:
\nbstart: 170024\nkeyword:
\nvararg: \n1: \n" + ] + "return:170001:200004" -> "function:170008:200004" + "funcargs:170016:170024" [ + label="funcargs\l\l" + tooltip="start: 170016\nfinish: 170024\n1: \n2: \n" + ] + "function:170008:200004" -> "funcargs:170016:170024" + "local:170017:170018" [ + label="local\lx\l" + tooltip="start: 170017\nfinish: 170018\neffect: 170018\n1: x\n" + ] + "funcargs:170016:170024" -> "local:170017:170018" + "...:170020:170023" [ + label="...\l\l" + tooltip="start: 170020\nfinish: 170023\nref:
\n1: ...\n" + ] + "funcargs:170016:170024" -> "...:170020:170023" + "repeat:180002:190028" [ + label="repeat\l\l" + tooltip="start: 180002\nfinish: 190028\nfilter: \nkeyword:
\nbstart: 180008\n" + ] + "function:170008:200004" -> "repeat:180002:190028" + "binary:190008:190028" [ + label="binary\l>\l" + tooltip="start: 190008\nfinish: 190028\nop: >\n1: \n2: \n" + ] + "repeat:180002:190028" -> "binary:190008:190028" + "call:190008:190024" [ + label="call\l\l" + tooltip="start: 190008\nfinish: 190024\nargs: \nnode: \n" + ] + "binary:190008:190028" -> "call:190008:190024" + "getglobal:190008:190014" [ + label="getglobal\lselect\l" + tooltip="start: 190008\nfinish: 190014\nnode: \n1: select\n" + ] + "call:190008:190024" -> "getglobal:190008:190014" + "callargs:190014:190024" [ + label="callargs\l\l" + tooltip="start: 190014\nfinish: 190024\n1: \n2: \n" + ] + "call:190008:190024" -> "callargs:190014:190024" + "string:190015:190018" [ + label="string\l#\l" + tooltip="start: 190015\nfinish: 190018\n1: #\n2: '\n" + ] + "callargs:190014:190024" -> "string:190015:190018" + "varargs:190020:190023" [ + label="varargs\l\l" + tooltip="start: 190020\nfinish: 190023\nnode: \n" + ] + "callargs:190014:190024" -> "varargs:190020:190023" + "integer:190027:190028" [ + label="integer\l0\l" + tooltip="start: 190027\nfinish: 190028\n1: 0\n" + ] + "binary:190008:190028" -> "integer:190027:190028" + "if:230000:290003" [ + label="if\l\l" + tooltip="start: 230000\nfinish: 290003\n1: \n2: \n3: \n" + ] + "main:0:300000" -> "if:230000:290003" + "ifblock:230000:250000" [ + label="ifblock\l\l" + tooltip="start: 230000\nfinish: 250000\nbstart: 230009\nfilter: \nhasReturn: true\nkeyword:
\n1: \n" + ] + "if:230000:290003" -> "ifblock:230000:250000" + "getglobal:230003:230004" [ + label="getglobal\lx\l" + tooltip="start: 230003\nfinish: 230004\nnode: \n1: x\n" + ] + "ifblock:230000:250000" -> "getglobal:230003:230004" + "return:240001:240048" [ + label="return\l\l" + tooltip="start: 240001\nfinish: 240048\n1: \n" + ] + "ifblock:230000:250000" -> "return:240001:240048" + "binary:240008:240048" [ + label="binary\lor\l" + tooltip="start: 240008\nfinish: 240048\nop: \n1: \n2: \n" + ] + "return:240001:240048" -> "binary:240008:240048" + "binary:240008:240023" [ + label="binary\lor\l" + tooltip="start: 240008\nfinish: 240023\nop: \n1: \n2: \n" + ] + "binary:240008:240048" -> "binary:240008:240023" + "binary:240008:240013" [ + label="binary\l<\l" + tooltip="start: 240008\nfinish: 240013\nop: \n1: \n2: \n" + ] + "binary:240008:240023" -> "binary:240008:240013" + "getglobal:240008:240009" [ + label="getglobal\lx\l" + tooltip="start: 240008\nfinish: 240009\nnode: \n1: x\n" + ] + "binary:240008:240013" -> "getglobal:240008:240009" + "integer:240012:240013" [ + label="integer\l0\l" + tooltip="start: 240012\nfinish: 240013\n1: 0\n" + ] + "binary:240008:240013" -> "integer:240012:240013" + "binary:240017:240023" [ + label="binary\l>=\l" + tooltip="start: 240017\nfinish: 240023\nop: =>\n1: \n2: \n" + ] + "binary:240008:240023" -> "binary:240017:240023" + "getglobal:240017:240018" [ + label="getglobal\lx\l" + tooltip="start: 240017\nfinish: 240018\nnode: \n1: x\n" + ] + "binary:240017:240023" -> "getglobal:240017:240018" + "integer:240022:240023" [ + label="integer\l0\l" + tooltip="start: 240022\nfinish: 240023\n1: 0\n" + ] + "binary:240017:240023" -> "integer:240022:240023" + "binary:240027:240048" [ + label="binary\land\l" + tooltip="start: 240027\nfinish: 240048\nop: \n1: \n2: \n" + ] + "binary:240008:240048" -> "binary:240027:240048" + "binary:240027:240037" [ + label="binary\l<=\l" + tooltip="start: 240027\nfinish: 240037\nop: \n1: \n2: \n" + ] + "binary:240027:240048" -> "binary:240027:240037" + "unary:240027:240032" [ + label="unary\lnot\l" + tooltip="start: 240027\nfinish: 240032\nop: \n1: \n" + ] + "binary:240027:240037" -> "unary:240027:240032" + "getglobal:240031:240032" [ + label="getglobal\lx\l" + tooltip="start: 240031\nfinish: 240032\nnode: \n1: x\n" + ] + "unary:240027:240032" -> "getglobal:240031:240032" + "integer:240036:240037" [ + label="integer\l0\l" + tooltip="start: 240036\nfinish: 240037\n1: 0\n" + ] + "binary:240027:240037" -> "integer:240036:240037" + "binary:240042:240048" [ + label="binary\l>\l" + tooltip="start: 240042\nfinish: 240048\nop: >\n1: \n2: \n" + ] + "binary:240027:240048" -> "binary:240042:240048" + "unary:240042:240044" [ + label="unary\l-\l" + tooltip="start: 240042\nfinish: 240044\nop: \n1: \n" + ] + "binary:240042:240048" -> "unary:240042:240044" + "getglobal:240043:240044" [ + label="getglobal\lx\l" + tooltip="start: 240043\nfinish: 240044\nnode: \n1: x\n" + ] + "unary:240042:240044" -> "getglobal:240043:240044" + "integer:240047:240048" [ + label="integer\l7\l" + tooltip="start: 240047\nfinish: 240048\n1: 7\n" + ] + "binary:240042:240048" -> "integer:240047:240048" + "elseifblock:250000:270000" [ + label="elseifblock\l\l" + tooltip="start: 250000\nfinish: 270000\nbstart: 250013\nfilter: \nhasReturn: true\nkeyword:
\n1: \n" + ] + "if:230000:290003" -> "elseifblock:250000:270000" + "getglobal:250007:250008" [ + label="getglobal\ly\l" + tooltip="start: 250007\nfinish: 250008\nnode: \n1: y\n" + ] + "elseifblock:250000:270000" -> "getglobal:250007:250008" + "return:260001:260011" [ + label="return\l\l" + tooltip="start: 260001\nfinish: 260011\n1: \n" + ] + "elseifblock:250000:270000" -> "return:260001:260011" + "number:260008:260011" [ + label="number\l5.7\l" + tooltip="start: 260008\nfinish: 260011\n1: 5.7\n" + ] + "return:260001:260011" -> "number:260008:260011" + "elseblock:270000:290000" [ + label="elseblock\l\l" + tooltip="start: 270000\nfinish: 290000\nkeyword:
\nbstart: 270004\nhasReturn: true\n1: \n" + ] + "if:230000:290003" -> "elseblock:270000:290000" + "return:280001:280091" [ + label="return\l\l" + tooltip="start: 280001\nfinish: 280091\n1: \n" + ] + "elseblock:270000:290000" -> "return:280001:280091" + "string:280008:280091" [ + label="string\la very long strin...\l" + tooltip="start: 280008\nfinish: 280091\n1: a very long strin...\n2: \"\n" + ] + "return:280001:280091" -> "string:280008:280091" +} diff --git a/test/cli/visualize/testdata/all-types.txt b/test/cli/visualize/testdata/all-types.txt new file mode 100644 index 000000000..167bd54b6 --- /dev/null +++ b/test/cli/visualize/testdata/all-types.txt @@ -0,0 +1,30 @@ +foo = {x = 5, bar = 6, ["baz"] = 7} +foo.y = foo.x + 1 +foo[1] = foo[0] +function foo:someMethod() return false end +local s = 0 +while s < 10 do + s = s + (foo:someMethod(i, 10) or 10) +end +print(s) +for j = 10, 1, -1 do + for i in ipairs(s) do + goto foolabel + ::foolabel:: + end +end + +function foo() + return function(x, ...) + repeat + until select('#', ...) > 0 + end +end + +if x then + return x < 0 or x >= 0 or not x <= 0 and -x > 7 +elseif y then + return 5.7 +else + return "a very long string that should get shortened to not destroy the layout completely" +end diff --git a/test/cli/visualize/testdata/shorten-names-expected.txt b/test/cli/visualize/testdata/shorten-names-expected.txt new file mode 100644 index 000000000..c03a697a5 --- /dev/null +++ b/test/cli/visualize/testdata/shorten-names-expected.txt @@ -0,0 +1,327 @@ +digraph AST { + node [shape = rect] + "main:0:170009" [ + label="main\l\l" + tooltip="start: 0\nfinish: 170009\nlocals:
\nstate:
\n1: \n2: \n3: \n4: \n5: \n6: \n7: \n8: \n9: \n10: \n11: \n12: \n13: \n14: \n15: \n15..17: (...)\n" + ] + "setglobal:0:1" [ + label="setglobal\ls\l" + tooltip="start: 0\nfinish: 1\nrange: 101\nvalue: \nnode: \n1: s\n" + ] + "main:0:170009" -> "setglobal:0:1" + "string:4:101" [ + label="string\lvery long string ...\l" + tooltip="start: 4\nfinish: 101\n1: very long string ...\n2: \"\n" + ] + "setglobal:0:1" -> "string:4:101" + "setglobal:10000:10001" [ + label="setglobal\ls\l" + tooltip="start: 10000\nfinish: 10001\nrange: 10031\nvalue: \nnode: \n1: s\n" + ] + "main:0:170009" -> "setglobal:10000:10001" + "string:10004:10031" [ + label="string\lstring\\nwith\\n\...\l" + tooltip="start: 10004\nfinish: 10031\nescs:
\n1: string\\nwith\\n\...\n2: \"\n" + ] + "setglobal:10000:10001" -> "string:10004:10031" + "setglobal:20000:20001" [ + label="setglobal\lx\l" + tooltip="start: 20000\nfinish: 20001\nrange: 20009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:20000:20001" + "binary:20004:20009" [ + label="binary\l+\l" + tooltip="start: 20004\nfinish: 20009\nop: \n1: \n2: \n" + ] + "setglobal:20000:20001" -> "binary:20004:20009" + "getglobal:20004:20005" [ + label="getglobal\lx\l" + tooltip="start: 20004\nfinish: 20005\nnode: \n1: x\n" + ] + "binary:20004:20009" -> "getglobal:20004:20005" + "integer:20008:20009" [ + label="integer\l1\l" + tooltip="start: 20008\nfinish: 20009\n1: 1\n" + ] + "binary:20004:20009" -> "integer:20008:20009" + "setglobal:30000:30001" [ + label="setglobal\lx\l" + tooltip="start: 30000\nfinish: 30001\nrange: 30009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:30000:30001" + "binary:30004:30009" [ + label="binary\l+\l" + tooltip="start: 30004\nfinish: 30009\nop: \n1: \n2: \n" + ] + "setglobal:30000:30001" -> "binary:30004:30009" + "getglobal:30004:30005" [ + label="getglobal\lx\l" + tooltip="start: 30004\nfinish: 30005\nnode: \n1: x\n" + ] + "binary:30004:30009" -> "getglobal:30004:30005" + "integer:30008:30009" [ + label="integer\l1\l" + tooltip="start: 30008\nfinish: 30009\n1: 1\n" + ] + "binary:30004:30009" -> "integer:30008:30009" + "setglobal:40000:40001" [ + label="setglobal\lx\l" + tooltip="start: 40000\nfinish: 40001\nrange: 40009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:40000:40001" + "binary:40004:40009" [ + label="binary\l+\l" + tooltip="start: 40004\nfinish: 40009\nop: \n1: \n2: \n" + ] + "setglobal:40000:40001" -> "binary:40004:40009" + "getglobal:40004:40005" [ + label="getglobal\lx\l" + tooltip="start: 40004\nfinish: 40005\nnode: \n1: x\n" + ] + "binary:40004:40009" -> "getglobal:40004:40005" + "integer:40008:40009" [ + label="integer\l1\l" + tooltip="start: 40008\nfinish: 40009\n1: 1\n" + ] + "binary:40004:40009" -> "integer:40008:40009" + "setglobal:50000:50001" [ + label="setglobal\lx\l" + tooltip="start: 50000\nfinish: 50001\nrange: 50009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:50000:50001" + "binary:50004:50009" [ + label="binary\l+\l" + tooltip="start: 50004\nfinish: 50009\nop: \n1: \n2: \n" + ] + "setglobal:50000:50001" -> "binary:50004:50009" + "getglobal:50004:50005" [ + label="getglobal\lx\l" + tooltip="start: 50004\nfinish: 50005\nnode: \n1: x\n" + ] + "binary:50004:50009" -> "getglobal:50004:50005" + "integer:50008:50009" [ + label="integer\l1\l" + tooltip="start: 50008\nfinish: 50009\n1: 1\n" + ] + "binary:50004:50009" -> "integer:50008:50009" + "setglobal:60000:60001" [ + label="setglobal\lx\l" + tooltip="start: 60000\nfinish: 60001\nrange: 60009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:60000:60001" + "binary:60004:60009" [ + label="binary\l+\l" + tooltip="start: 60004\nfinish: 60009\nop: \n1: \n2: \n" + ] + "setglobal:60000:60001" -> "binary:60004:60009" + "getglobal:60004:60005" [ + label="getglobal\lx\l" + tooltip="start: 60004\nfinish: 60005\nnode: \n1: x\n" + ] + "binary:60004:60009" -> "getglobal:60004:60005" + "integer:60008:60009" [ + label="integer\l1\l" + tooltip="start: 60008\nfinish: 60009\n1: 1\n" + ] + "binary:60004:60009" -> "integer:60008:60009" + "setglobal:70000:70001" [ + label="setglobal\lx\l" + tooltip="start: 70000\nfinish: 70001\nrange: 70009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:70000:70001" + "binary:70004:70009" [ + label="binary\l+\l" + tooltip="start: 70004\nfinish: 70009\nop: \n1: \n2: \n" + ] + "setglobal:70000:70001" -> "binary:70004:70009" + "getglobal:70004:70005" [ + label="getglobal\lx\l" + tooltip="start: 70004\nfinish: 70005\nnode: \n1: x\n" + ] + "binary:70004:70009" -> "getglobal:70004:70005" + "integer:70008:70009" [ + label="integer\l1\l" + tooltip="start: 70008\nfinish: 70009\n1: 1\n" + ] + "binary:70004:70009" -> "integer:70008:70009" + "setglobal:80000:80001" [ + label="setglobal\lx\l" + tooltip="start: 80000\nfinish: 80001\nrange: 80009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:80000:80001" + "binary:80004:80009" [ + label="binary\l+\l" + tooltip="start: 80004\nfinish: 80009\nop: \n1: \n2: \n" + ] + "setglobal:80000:80001" -> "binary:80004:80009" + "getglobal:80004:80005" [ + label="getglobal\lx\l" + tooltip="start: 80004\nfinish: 80005\nnode: \n1: x\n" + ] + "binary:80004:80009" -> "getglobal:80004:80005" + "integer:80008:80009" [ + label="integer\l1\l" + tooltip="start: 80008\nfinish: 80009\n1: 1\n" + ] + "binary:80004:80009" -> "integer:80008:80009" + "setglobal:90000:90001" [ + label="setglobal\lx\l" + tooltip="start: 90000\nfinish: 90001\nrange: 90009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:90000:90001" + "binary:90004:90009" [ + label="binary\l+\l" + tooltip="start: 90004\nfinish: 90009\nop: \n1: \n2: \n" + ] + "setglobal:90000:90001" -> "binary:90004:90009" + "getglobal:90004:90005" [ + label="getglobal\lx\l" + tooltip="start: 90004\nfinish: 90005\nnode: \n1: x\n" + ] + "binary:90004:90009" -> "getglobal:90004:90005" + "integer:90008:90009" [ + label="integer\l1\l" + tooltip="start: 90008\nfinish: 90009\n1: 1\n" + ] + "binary:90004:90009" -> "integer:90008:90009" + "setglobal:100000:100001" [ + label="setglobal\lx\l" + tooltip="start: 100000\nfinish: 100001\nrange: 100009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:100000:100001" + "binary:100004:100009" [ + label="binary\l+\l" + tooltip="start: 100004\nfinish: 100009\nop: \n1: \n2: \n" + ] + "setglobal:100000:100001" -> "binary:100004:100009" + "getglobal:100004:100005" [ + label="getglobal\lx\l" + tooltip="start: 100004\nfinish: 100005\nnode: \n1: x\n" + ] + "binary:100004:100009" -> "getglobal:100004:100005" + "integer:100008:100009" [ + label="integer\l1\l" + tooltip="start: 100008\nfinish: 100009\n1: 1\n" + ] + "binary:100004:100009" -> "integer:100008:100009" + "setglobal:110000:110001" [ + label="setglobal\lx\l" + tooltip="start: 110000\nfinish: 110001\nrange: 110009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:110000:110001" + "binary:110004:110009" [ + label="binary\l+\l" + tooltip="start: 110004\nfinish: 110009\nop: \n1: \n2: \n" + ] + "setglobal:110000:110001" -> "binary:110004:110009" + "getglobal:110004:110005" [ + label="getglobal\lx\l" + tooltip="start: 110004\nfinish: 110005\nnode: \n1: x\n" + ] + "binary:110004:110009" -> "getglobal:110004:110005" + "integer:110008:110009" [ + label="integer\l1\l" + tooltip="start: 110008\nfinish: 110009\n1: 1\n" + ] + "binary:110004:110009" -> "integer:110008:110009" + "setglobal:120000:120001" [ + label="setglobal\lx\l" + tooltip="start: 120000\nfinish: 120001\nrange: 120009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:120000:120001" + "binary:120004:120009" [ + label="binary\l+\l" + tooltip="start: 120004\nfinish: 120009\nop: \n1: \n2: \n" + ] + "setglobal:120000:120001" -> "binary:120004:120009" + "getglobal:120004:120005" [ + label="getglobal\lx\l" + tooltip="start: 120004\nfinish: 120005\nnode: \n1: x\n" + ] + "binary:120004:120009" -> "getglobal:120004:120005" + "integer:120008:120009" [ + label="integer\l1\l" + tooltip="start: 120008\nfinish: 120009\n1: 1\n" + ] + "binary:120004:120009" -> "integer:120008:120009" + "setglobal:130000:130001" [ + label="setglobal\lx\l" + tooltip="start: 130000\nfinish: 130001\nrange: 130009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:130000:130001" + "binary:130004:130009" [ + label="binary\l+\l" + tooltip="start: 130004\nfinish: 130009\nop: \n1: \n2: \n" + ] + "setglobal:130000:130001" -> "binary:130004:130009" + "getglobal:130004:130005" [ + label="getglobal\lx\l" + tooltip="start: 130004\nfinish: 130005\nnode: \n1: x\n" + ] + "binary:130004:130009" -> "getglobal:130004:130005" + "integer:130008:130009" [ + label="integer\l1\l" + tooltip="start: 130008\nfinish: 130009\n1: 1\n" + ] + "binary:130004:130009" -> "integer:130008:130009" + "setglobal:140000:140001" [ + label="setglobal\lx\l" + tooltip="start: 140000\nfinish: 140001\nrange: 140009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:140000:140001" + "binary:140004:140009" [ + label="binary\l+\l" + tooltip="start: 140004\nfinish: 140009\nop: \n1: \n2: \n" + ] + "setglobal:140000:140001" -> "binary:140004:140009" + "getglobal:140004:140005" [ + label="getglobal\lx\l" + tooltip="start: 140004\nfinish: 140005\nnode: \n1: x\n" + ] + "binary:140004:140009" -> "getglobal:140004:140005" + "integer:140008:140009" [ + label="integer\l1\l" + tooltip="start: 140008\nfinish: 140009\n1: 1\n" + ] + "binary:140004:140009" -> "integer:140008:140009" + "setglobal:160000:160001" [ + label="setglobal\lx\l" + tooltip="start: 160000\nfinish: 160001\nrange: 160009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:160000:160001" + "binary:160004:160009" [ + label="binary\l+\l" + tooltip="start: 160004\nfinish: 160009\nop: \n1: \n2: \n" + ] + "setglobal:160000:160001" -> "binary:160004:160009" + "getglobal:160004:160005" [ + label="getglobal\lx\l" + tooltip="start: 160004\nfinish: 160005\nnode: \n1: x\n" + ] + "binary:160004:160009" -> "getglobal:160004:160005" + "integer:160008:160009" [ + label="integer\l1\l" + tooltip="start: 160008\nfinish: 160009\n1: 1\n" + ] + "binary:160004:160009" -> "integer:160008:160009" + "setglobal:170000:170001" [ + label="setglobal\lx\l" + tooltip="start: 170000\nfinish: 170001\nrange: 170009\nvalue: \nnode: \n1: x\n" + ] + "main:0:170009" -> "setglobal:170000:170001" + "binary:170004:170009" [ + label="binary\l+\l" + tooltip="start: 170004\nfinish: 170009\nop: \n1: \n2: \n" + ] + "setglobal:170000:170001" -> "binary:170004:170009" + "getglobal:170004:170005" [ + label="getglobal\lx\l" + tooltip="start: 170004\nfinish: 170005\nnode: \n1: x\n" + ] + "binary:170004:170009" -> "getglobal:170004:170005" + "integer:170008:170009" [ + label="integer\l1\l" + tooltip="start: 170008\nfinish: 170009\n1: 1\n" + ] + "binary:170004:170009" -> "integer:170008:170009" +} diff --git a/test/cli/visualize/testdata/shorten-names.txt b/test/cli/visualize/testdata/shorten-names.txt new file mode 100644 index 000000000..90aa7f0e2 --- /dev/null +++ b/test/cli/visualize/testdata/shorten-names.txt @@ -0,0 +1,18 @@ +s = "very long string that is very long and would be annoying if it was fully included in the output" +s = "string\nwith\n\new\nlines" +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +x = x + 1 +-- more than 15 lines should be shortened for tooltip in main +x = x + 1 +x = x + 1 \ No newline at end of file diff --git a/test/code_action/init.lua b/test/code_action/init.lua index 67528d593..75c116a4b 100644 --- a/test/code_action/init.lua +++ b/test/code_action/init.lua @@ -2,48 +2,77 @@ local core = require 'core.code-action' local files = require 'files' local lang = require 'language' local catch = require 'catch' +local furi = require 'file-uri' rawset(_G, 'TEST', true) local EXISTS = {} -local function eq(a, b) - if a == EXISTS and b ~= nil then +local function eq(expected, result) + if expected == EXISTS and result ~= nil then return true end - if b == EXISTS and a ~= nil then + if result == EXISTS and expected ~= nil then return true end - local tp1, tp2 = type(a), type(b) + local tp1, tp2 = type(expected), type(result) if tp1 ~= tp2 then - return false + return false, string.format(": expected type %s, got %s", tp1, tp2) end if tp1 == 'table' then local mark = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false + for k in pairs(expected) do + local ok, err = eq(expected[k], result[k]) + if not ok then + return false, string.format(".%s%s", k, err) end mark[k] = true end - for k in pairs(b) do + for k in pairs(result) do if not mark[k] then - return false + return false, string.format(".%s: missing key in result", k) end end return true end - return a == b + return expected == result, string.format(": expected %s, got %s", expected, result) end function TEST(script) return function (expect) local newScript, catched = catch(script, '?') - files.setText('', newScript) - local results = core('', catched['?'][1][1], catched['?'][1][2]) + files.setText(TESTURI, newScript) + local results = core(TESTURI, catched['?'][1][1], catched['?'][1][2]) assert(results) assert(eq(expect, results)) - files.remove('') + files.remove(TESTURI) + end +end + +---@param testfiles [string, {path:string, content:string}] +local function TEST_CROSSFILE(testfiles) + local mainscript = table.remove(testfiles, 1) + return function(expected) + for _, data in ipairs(testfiles) do + local uri = furi.encode(TESTROOT .. data.path) + files.setText(uri, data.content) + files.compileState(uri) + end + + local newScript, catched = catch(mainscript, '?') + files.setText(TESTURI, newScript) + files.compileState(TESTURI) + + local _ = function () + for _, info in ipairs(testfiles) do + files.remove(furi.encode(TESTROOT .. info.path)) + end + files.remove(TESTURI) + end + + local results = core(TESTURI, catched['?'][1][1], catched['?'][1][2]) + assert(results) + assert(eq(expected, results)) end end @@ -154,3 +183,97 @@ local t = { -- edit = EXISTS, -- }, --} + +TEST_CROSSFILE { +[[ + .myFunction() +]], + { + path = 'unrequiredModule.lua', + content = [[ + local m = {} + m.myFunction = print + return m + ]] + } +} { + { + title = lang.script('ACTION_AUTOREQUIRE', 'unrequiredModule', 'unrequiredModule'), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = TESTURI, + target = furi.encode(TESTROOT .. 'unrequiredModule.lua'), + name = 'unrequiredModule', + requireName = 'unrequiredModule' + }, + }, + } + } +} + +TEST_CROSSFILE { +[[ + .myFunction() +]], + { + path = 'myModule/init.lua', + content = [[ + local m = {} + m.myFunction = print + return m + ]] + } +} { + { + title = lang.script('ACTION_AUTOREQUIRE', 'myModule.init', 'myModule'), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = TESTURI, + target = furi.encode(TESTROOT .. 'myModule/init.lua'), + name = 'myModule', + requireName = 'myModule.init' + }, + }, + } + }, + { + title = lang.script('ACTION_AUTOREQUIRE', 'init', 'myModule'), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = TESTURI, + target = furi.encode(TESTROOT .. 'myModule/init.lua'), + name = 'myModule', + requireName = 'init' + }, + }, + } + }, + { + title = lang.script('ACTION_AUTOREQUIRE', 'myModule', 'myModule'), + kind = 'refactor.rewrite', + command = { + title = 'autoRequire', + command = 'lua.autoRequire', + arguments = { + { + uri = TESTURI, + target = furi.encode(TESTROOT .. 'myModule/init.lua'), + name = 'myModule', + requireName = 'myModule' + }, + }, + } + }, +} diff --git a/test/command/auto-require.lua b/test/command/auto-require.lua index 38cc00120..8c6a8971b 100644 --- a/test/command/auto-require.lua +++ b/test/command/auto-require.lua @@ -12,6 +12,7 @@ assert(applyAutoRequire) local originEditText = client.editText local EditResult +---@diagnostic disable-next-line: duplicate-set-field client.editText = function (uri, edits) EditResult = edits[1] end @@ -19,12 +20,12 @@ end function TEST(text) return function (name) return function (expect) - files.setText('', text) + files.setText(TESTURI, text) EditResult = nil - local row, fmt = findInsertRow('') - applyAutoRequire('', row, name, name, fmt) + local row, fmt = findInsertRow(TESTURI) + applyAutoRequire(TESTURI, row, name, name, fmt) assert(util.equal(EditResult, expect)) - files.remove('') + files.remove(TESTURI) end end end diff --git a/test/completion/common.lua b/test/completion/common.lua index f1100e366..3ea02ed7e 100644 --- a/test/completion/common.lua +++ b/test/completion/common.lua @@ -82,7 +82,7 @@ ass ]] { { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Function, }, } @@ -93,7 +93,7 @@ ass ]] { { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Function, }, } @@ -104,11 +104,11 @@ ass ]] { { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Function, }, { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Snippet, }, } @@ -130,11 +130,11 @@ _G.ass ]] { { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Function, }, { - label = 'assert(v, message)', + label = 'assert(v, message, ...)', kind = define.CompletionItemKind.Snippet, }, } @@ -630,7 +630,7 @@ self.results.list[#self.re] }, { label = 'results', - kind = define.CompletionItemKind.Field, + kind = define.CompletionItemKind.Text, }, } @@ -1006,12 +1006,12 @@ t. ]] { { - label = 'a.b.c', + label = "'a.b.c'", kind = define.CompletionItemKind.Field, textEdit = { start = 40002, finish = 40002, - newText = '["a.b.c"]', + newText = "['a.b.c']", }, additionalTextEdits = { { @@ -1032,12 +1032,12 @@ t. ]] { { - label = 'a.b.c', + label = "'a.b.c'", kind = define.CompletionItemKind.Field, textEdit = { start = 40005, finish = 40005, - newText = '["a.b.c"]', + newText = "['a.b.c']", }, additionalTextEdits = { { @@ -1058,7 +1058,7 @@ t[''] ]] { { - label = 'a.b.c', + label = "'a.b.c'", kind = define.CompletionItemKind.Field, textEdit = { start = 40003, @@ -1069,22 +1069,74 @@ t[''] } TEST [[ -_ENV['z.b.c'] = {} +_G['z.b.c'] = {} + +z +]] +{ + { + label = "'z.b.c'", + kind = define.CompletionItemKind.Field, + textEdit = { + start = 20000, + finish = 20001, + newText = "_ENV['z.b.c']", + }, + }, +} + +config.set(nil, 'Lua.runtime.version', 'Lua 5.1') + +TEST [[ +_G['z.b.c'] = {} z ]] { { - label = 'z.b.c', + label = "'z.b.c'", kind = define.CompletionItemKind.Field, textEdit = { start = 20000, finish = 20001, - newText = '_ENV["z.b.c"]', + newText = "_G['z.b.c']", + }, + }, +} + +config.set(nil, 'Lua.runtime.version', 'Lua 5.4') + +TEST [[ +ไธญๆ–‡ๅญ—ๆฎต = 1 + +ไธญๆ–‡ +]] +{ + { + label = 'ไธญๆ–‡ๅญ—ๆฎต', + kind = define.CompletionItemKind.Enum, + textEdit = { + start = 20000, + finish = 20006, + newText = '_ENV["ไธญๆ–‡ๅญ—ๆฎต"]', }, }, } +config.set(nil, 'Lua.runtime.unicodeName', true) +TEST [[ +ไธญๆ–‡ๅญ—ๆฎต = 1 + +ไธญๆ–‡ +]] +{ + { + label = 'ไธญๆ–‡ๅญ—ๆฎต', + kind = define.CompletionItemKind.Enum, + }, +} +config.set(nil, 'Lua.runtime.unicodeName', false) + TEST [[ io.close(1, ) ]] @@ -3486,7 +3538,7 @@ TEST [[ require '' ]] (function (results) - assert(#results == 11, require 'utility'.dump(results)) + assert(#results == 9, require 'utility'.dump(results)) end) TEST [[ @@ -3698,6 +3750,97 @@ f() }, } +TEST [[ +---@enum A +ppp.fff = { + x = 1, + y = 'ss', +} + +---@param x A +local function f(x) end + +f() +]] +{ + { + label = 'ppp.fff.x', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = 'ppp.fff.y', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '1', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"ss"', + kind = define.CompletionItemKind.EnumMember, + }, +} + +TEST [[ +local x = 1 +local y = 2 + +---@enum Enum +local t = { + x = x, + y = y, +} + +---@param p Enum +local function f(p) end + +f() +]] +{ + { + label = 't.x', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = 't.y', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '1', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '2', + kind = define.CompletionItemKind.EnumMember, + }, +} + +TEST [[ +local x = 1 +local y = 2 + +---@enum(key) Enum +local t = { + x = x, + y = y, +} + +---@param p Enum +local function f(p) end + +f() +]] +{ + { + label = '"x"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"y"', + kind = define.CompletionItemKind.EnumMember, + }, +} + TEST [[ -- @@ -3719,3 +3862,575 @@ TEST [[ local x = function (x, y) end ]] (EXISTS) + +TEST [[ +--- +local x = function (x, y) end +]] +(EXISTS) + +TEST [[ +local x = { + +}) +]] +(function (results) + for _, res in ipairs(results) do + assert(res.label ~= 'do') + end +end) + +TEST [[ +---@class Options +---@field page number +---@field active boolean + +---@param opts Options +local function acceptOptions(opts) end + +acceptOptions({ + +}) +]] +(function (results) + assert(#results == 2) +end) + +TEST [[ +local t1 = {} + +t1.A = {} +t1.A.B = {} +t1.A.B.C = 1 + +local t2 = t1 + +print(t2.A.) +]] +{ + { + label = 'B', + kind = define.CompletionItemKind.Field, + }, +} + +TEST [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +local function fff(...) +end + +fff +]] +{ + { + label = 'fff(x)', + kind = define.CompletionItemKind.Function, + }, + { + label = 'fff(x, y)', + kind = define.CompletionItemKind.Function, + }, +} + +TEST [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +function fff(...) +end + +fff +]] +{ + { + label = 'fff(x)', + kind = define.CompletionItemKind.Function, + }, + { + label = 'fff(x, y)', + kind = define.CompletionItemKind.Function, + }, +} + +TEST [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +function t.fff(...) +end + +t.fff +]] +{ + { + label = 'fff(x)', + kind = define.CompletionItemKind.Function, + }, + { + label = 'fff(x, y)', + kind = define.CompletionItemKind.Function, + }, +} + +TEST [[ +---@class A +---@field private x number +---@field y number + +---@type A +local t + +t. +]] +{ + { + label = 'y', + kind = define.CompletionItemKind.Field, + }, +} + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@class B: A +local t + +t. +]] +{ + { + label = 'y', + kind = define.CompletionItemKind.Field, + }, + { + label = 'z', + kind = define.CompletionItemKind.Field, + }, +} + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@class B: A + +---@type B +local t + +t. +]] +{ + { + label = 'z', + kind = define.CompletionItemKind.Field, + }, +} + +TEST [[ +---@class ABCD + +---@see ABCD +]] +{ + { + label = 'ABCD', + kind = define.CompletionItemKind.Class, + textEdit = { + newText = 'ABCD', + start = 20008, + finish = 20012, + } + }, +} + +TEST [[ +---@class A +---@field f fun(x: string): string + +---@type A +local t = { + f = +} +]] +{ + { + label = 'fun(x: string):string', + kind = define.CompletionItemKind.Function, + } +} + +TEST [[ +---@type table +local x = { + a = 1, + b = 2, + c = 3 +} + +x. +]] +{ + { + label = 'a', + kind = define.CompletionItemKind.Enum, + }, + { + label = 'b', + kind = define.CompletionItemKind.Enum, + }, + { + label = 'c', + kind = define.CompletionItemKind.Enum, + }, +} + +TEST [[ +---@param x {damage: integer, count: integer, health:integer} +local function f(x) end + +f {} +]] +{ + { + label = 'count', + kind = define.CompletionItemKind.Property, + }, + { + label = 'damage', + kind = define.CompletionItemKind.Property, + }, + { + label = 'health', + kind = define.CompletionItemKind.Property, + }, +} + +TEST [[ +---@alias Foo Foo[] +---@type Foo +local foo +foo = {""} +]] +(EXISTS) + +TEST [[ +---@class c +---@field abc fun() +---@field abc2 fun() + +---@param p c +local function f(p) end + +f({ + abc = function(s) + local abc3 + abc + end, +}) +]] +{ + { + label = 'abc3', + kind = define.CompletionItemKind.Variable, + }, + { + label = 'abc', + kind = define.CompletionItemKind.Text, + }, + { + label = 'abc2', + kind = define.CompletionItemKind.Text, + }, +} + +TEST [[ +while true do + continue +end +]] +{ + { + label = 'continue', + kind = define.CompletionItemKind.Keyword, + }, + { + label = 'goto continue ..', + kind = define.CompletionItemKind.Snippet, + additionalTextEdits = { + { + start = 10004, + finish = 10004, + newText = 'goto ', + }, + { + start = 20000, + finish = 20000, + newText = ' ::continue::\n', + }, + } + }, +} + +TEST [[ +while true do + goto continue +end +]] +{ + { + label = 'continue', + kind = define.CompletionItemKind.Keyword, + }, + { + label = 'goto continue ..', + kind = define.CompletionItemKind.Snippet, + additionalTextEdits = { + { + start = 20000, + finish = 20000, + newText = ' ::continue::\n', + } + } + }, +} + +TEST [[ +while true do + goto continue + ::continue:: +end +]] +{ + { + label = 'continue', + kind = define.CompletionItemKind.Keyword, + }, + { + label = 'goto continue ..', + kind = define.CompletionItemKind.Snippet, + additionalTextEdits = { + } + }, +} + +Cared['description'] = true +TEST [[ +---@class Foo +---@field ['with quotes'] integer +---@field without_quotes integer + +---@type Foo +local bar = {} + +bar. +]] +{ + { + label = "'with quotes'", + kind = define.CompletionItemKind.Field, + textEdit = { + start = 70004, + finish = 70004, + newText = "['with quotes']" + }, + additionalTextEdits = { + { + start = 70003, + finish = 70004, + newText = '', + } + }, + description = [[ +```lua +(field) Foo['with quotes']: integer +```]] + }, + { + label = 'without_quotes', + kind = define.CompletionItemKind.Field, + description = [[ +```lua +(field) Foo.without_quotes: integer +```]] + }, +} +Cared['description'] = false + +TEST [[ +---@class A +local M = {} + +function M:method1() +end + +function M.static1(tt) +end + +function M:method2() +end + +function M.static2(tt) +end + +---@type A +local a + +a. +]] +{ + { + label ='static1(tt)', + kind = define.CompletionItemKind.Function, + }, + { + label ='static2(tt)', + kind = define.CompletionItemKind.Function, + }, + { + label ='method1(self)', + kind = define.CompletionItemKind.Method, + }, + { + label ='method2(self)', + kind = define.CompletionItemKind.Method, + }, +} + +TEST [[ +---@class A +local M = {} + +function M:method1() +end + +function M.static1(tt) +end + +function M:method2() +end + +function M.static2(tt) +end + +---@type A +local a + +a: +]] +{ + { + label ='method1()', + kind = define.CompletionItemKind.Method, + }, + { + label ='method2()', + kind = define.CompletionItemKind.Method, + }, + { + label ='static1()', + kind = define.CompletionItemKind.Function, + }, + { + label ='static2()', + kind = define.CompletionItemKind.Function, + }, +} + +TEST [[ +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = { + +} +]] +{ + { + label = 'x', + kind = define.CompletionItemKind.Property, + }, + { + label = 'z', + kind = define.CompletionItemKind.Property, + }, + { + label = 'y?', + kind = define.CompletionItemKind.Property, + }, +} + +TEST [[ +---@class A +---@field x number +---@field y? number +---@field z number + +---@param t A +local function f(t) end + +f { + +} +]] +{ + { + label = 'x', + kind = define.CompletionItemKind.Property, + }, + { + label = 'z', + kind = define.CompletionItemKind.Property, + }, + { + label = 'y?', + kind = define.CompletionItemKind.Property, + }, +} + +TEST [[ +---@class A +---@overload fun(x: {id: string}) + +---@generic T +---@param t `T` +---@return T +local function new(t) end + +new 'A' { + +} +]] +{ + { + label = 'id', + kind = define.CompletionItemKind.Property, + } +} + +TEST [[ +---@class namespace.A +---@overload fun(x: {id: string}) + +---@generic T +---@param t namespace.`T` +---@return T +local function new(t) end + +new 'A' { + +} +]] +{ + { + label = 'id', + kind = define.CompletionItemKind.Property, + } +} + diff --git a/test/completion/init.lua b/test/completion/init.lua index 4ae185cad..1ac7fd4b3 100644 --- a/test/completion/init.lua +++ b/test/completion/init.lua @@ -68,8 +68,8 @@ function TEST(script) ---@diagnostic disable: await-in-sync local newScript, catched = catch(script, '?') - files.setText('', newScript) - local state = files.getState('') + files.setText(TESTURI, newScript) + local state = files.getState(TESTURI) local inputPos = catched['?'][1][2] if ContinueTyping then local triggerCharacter = script:sub(inputPos - 1, inputPos - 1) @@ -77,7 +77,7 @@ function TEST(script) or triggerCharacter:find '%w_' then triggerCharacter = nil end - core.completion('', inputPos, triggerCharacter) + core.completion(TESTURI, inputPos, triggerCharacter) end local offset = guide.positionToOffset(state, inputPos) local triggerCharacter = script:sub(offset, offset) @@ -85,7 +85,7 @@ function TEST(script) or triggerCharacter:find '%w_' then triggerCharacter = nil end - local result = core.completion('', inputPos, triggerCharacter) + local result = core.completion(TESTURI, inputPos, triggerCharacter) if not expect then assert(result == nil) @@ -131,7 +131,7 @@ function TEST(script) assert(eq(expect, result)) end end - files.remove('') + files.remove(TESTURI) end end diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua index 3b0f348c0..fa099e47e 100644 --- a/test/crossfile/completion.lua +++ b/test/crossfile/completion.lua @@ -10,6 +10,7 @@ local define = require 'proto.define' rawset(_G, 'TEST', true) local CompletionItemKind = define.CompletionItemKind +local NeedRemoveMeta = false local EXISTS = {} @@ -60,7 +61,7 @@ function TEST(data) local mainUri local pos for _, info in ipairs(data) do - local uri = furi.encode(info.path) + local uri = furi.encode(TESTROOT .. info.path) local script = info.content or '' if info.main then local newScript, catched = catch(script, '?') @@ -69,11 +70,12 @@ function TEST(data) mainUri = uri end files.setText(uri, script) + files.compileState(uri) end local _ = function () for _, info in ipairs(data) do - files.remove(furi.encode(info.path)) + files.remove(furi.encode(TESTROOT .. info.path)) end end @@ -86,7 +88,9 @@ function TEST(data) assert(result ~= nil) result.complete = nil result.enableCommon = nil - removeMetas(result) + if NeedRemoveMeta then + removeMetas(result) + end for _, item in ipairs(result) do if item.id then local r = core.resolve(item.id) @@ -104,10 +108,59 @@ function TEST(data) : gsub('\r\n', '\n') end end + for _, eitem in ipairs(expect) do + if eitem['description'] then + eitem['description'] = eitem['description'] + : gsub('%$(.-)%$', _G) + end + end assert(result) assert(eq(expect, result)) end +local function WITH_CONFIG(cfg, f) + local prev = { } + for k, v in pairs(cfg) do + prev[k] = config.get(nil, k) + config.set(nil, k, v) + end + f() + for k, v in pairs(prev) do + config.set(nil, k, v) + end +end + +TEST { + { + path = 'abc.lua', + content = [[ + ---@meta + + ---@class A + ---@field f1 integer + ---@field f2 boolean + + ---@type A[] + X = {} +]], + }, + { + path = 'test.lua', + content = [[ X[1].]], + main = true, + }, + completion = { + { + label = 'f1', + kind = CompletionItemKind.Field, + }, + { + label = 'f2', + kind = CompletionItemKind.Field, + }, + } +} + TEST { { path = 'abc.lua', @@ -376,7 +429,7 @@ config.set(nil, 'Lua.runtime.path', { TEST { { - path = 'D:/xxxx/1.lua', + path = 'tt/xxxx/1.lua', content = '', }, { @@ -386,7 +439,7 @@ TEST { }, completion = { { - label = 'D:.xxxx', + label = 'tt.xxxx', kind = CompletionItemKind.File, textEdit = EXISTS, }, @@ -402,12 +455,12 @@ config.set(nil, 'Lua.runtime.path', originRuntimePath) local originRuntimePath = config.get(nil, 'Lua.runtime.path') config.set(nil, 'Lua.runtime.path', { - 'D:/?/1.lua', + 'tt/?/1.lua', }) TEST { { - path = 'D:/xxxx/1.lua', + path = 'tt/xxxx/1.lua', content = '', }, { @@ -456,6 +509,51 @@ TEST { config.set(nil, 'Lua.runtime.path', originRuntimePath) config.set(nil, 'Lua.completion.requireSeparator', originSeparator) +TEST { + { + path = 'abc.lua', + content = '---@meta _', + }, + { + path = 'test.lua', + content = 'require "a"', + main = true, + }, + completion = nil +} + +TEST { + { + path = 'abc.lua', + content = '---@meta xxxxx', + }, + { + path = 'test.lua', + content = 'require "a"', + main = true, + }, + completion = nil +} + +TEST { + { + path = 'abc.lua', + content = '---@meta xxxxx', + }, + { + path = 'test.lua', + content = 'require "xx"', + main = true, + }, + completion = { + { + label = 'xxxxx', + kind = CompletionItemKind.File, + textEdit = EXISTS, + } + } +} + TEST { { path = 'a.lua', @@ -615,6 +713,8 @@ TEST { completion = nil, } +NeedRemoveMeta = true + TEST { { path = 'f/a.lua' }, { path = 'f/b.lua' }, @@ -885,7 +985,7 @@ local z: table } } -if platform.OS == 'Windows' then +if platform.os == 'windows' then Cared['detail'] = true Cared['additionalTextEdits'] = true TEST { @@ -909,7 +1009,7 @@ TEST { kind = CompletionItemKind.Variable, detail = 'function', description = [[ -ไปŽ [myfunc.lua](file:///myfunc.lua) ไธญๅฏผๅ…ฅ +ไปŽ [myfunc.lua]($TESTROOTURI$myfunc.lua) ไธญๅฏผๅ…ฅ ```lua function (a: any, b: any) @@ -940,7 +1040,7 @@ TEST { kind = CompletionItemKind.Variable, detail = 'function', description = [[ -ไปŽ [dir\myfunc.lua](file:///dir/myfunc.lua) ไธญๅฏผๅ…ฅ +ไปŽ [dir\myfunc.lua]($TESTROOTURI$dir/myfunc.lua) ไธญๅฏผๅ…ฅ ```lua function (a: any, b: any) @@ -1018,3 +1118,74 @@ TEST { }, completion = EXISTS } + +-- Find obscured modules + +WITH_CONFIG({ + ["Lua.runtime.pathStrict"] = true, + ["Lua.runtime.path"] = { + "?/init.lua", + "sub/?/init.lua", + "obscure_path/?/?/init.lua" + }, +}, function() + TEST { + { path = 'myLib/init.lua', content = 'return {}' }, + { + path = 'main.lua', + main = true, + content = [[ + myLib + ]], + }, + completion = EXISTS + } + + TEST { + { path = 'sub/myLib/init.lua', content = 'return {}' }, + { + path = 'main.lua', + main = true, + content = [[ + myLib + ]], + }, + completion = EXISTS + } + + TEST { + { path = 'sub/myLib/sublib/init.lua', content = 'return {}' }, + { + path = 'main.lua', + main = true, + content = [[ + sublib + ]], + }, + completion = EXISTS + } + + TEST { + { path = 'sublib/init.lua', content = 'return {}' }, + { + path = 'main.lua', + main = true, + content = [[ + sublib + ]], + }, + completion = EXISTS + } + + TEST { + { path = 'obscure_path/myLib/obscure/myLib/obscure/init.lua', content = 'return {}' }, + { + path = 'main.lua', + main = true, + content = [[ + obscure + ]], + }, + completion = EXISTS + } +end) diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua index a9a8fab44..79d5afb9b 100644 --- a/test/crossfile/definition.lua +++ b/test/crossfile/definition.lua @@ -26,6 +26,7 @@ local function founded(targets, results) return true end +---@async function TEST(datas) local targetList = {} local sourceList @@ -52,6 +53,7 @@ function TEST(datas) sourceUri = uri end files.setText(uri, newScript) + files.compileState(uri) end local _ = function () @@ -486,7 +488,7 @@ TEST { { path = 'a.lua', content = [[ - local + local x return { = x, } @@ -739,7 +741,7 @@ TEST { } -if platform.OS == 'Linux' then +if platform.os == 'linux' then TEST { { diff --git a/test/crossfile/diagnostic.lua b/test/crossfile/diagnostic.lua index dd06351fd..1acf3fe2c 100644 --- a/test/crossfile/diagnostic.lua +++ b/test/crossfile/diagnostic.lua @@ -8,6 +8,7 @@ local catch = require 'catch' config.get(nil, 'Lua.diagnostics.neededFileStatus')['deprecated'] = 'Any' config.get(nil, 'Lua.diagnostics.neededFileStatus')['type-check'] = 'Any' +config.get(nil, 'Lua.diagnostics.neededFileStatus')['duplicate-set-field'] = 'Any' config.get(nil, 'Lua.diagnostics.neededFileStatus')['codestyle-check'] = 'None' rawset(_G, 'TEST', true) @@ -46,6 +47,7 @@ function TEST(datas) end data.content = newScript files.setText(uri, newScript) + files.compileState(uri) end local _ = function () @@ -56,20 +58,26 @@ function TEST(datas) local results = {} + local origins = {} for _, data in ipairs(datas) do local uri = furi.encode(data.path) core(uri, false, function (result) - results[#results+1] = { - result.start, - result.finish, - uri, - } + if result.code == datas.code then + results[#results+1] = { + result.start, + result.finish, + uri, + } + end + origins[#origins+1] = result end) end + assert(datas.code, 'Need code') assert(founded(targetList, results)) end TEST { + code = 'different-requires', { path = 'f/a.lua', content = '', @@ -85,6 +93,7 @@ TEST { } TEST { + code = 'different-requires', { path = 'f/a.lua', content = '', @@ -104,6 +113,7 @@ TEST { } TEST { + code = 'different-requires', { path = 'a.lua', content = '', @@ -123,6 +133,7 @@ TEST { } TEST { + code = 'different-requires', { path = 'a/init.lua', content = '', @@ -140,3 +151,78 @@ TEST { content = 'require "f.a"', }, } + +TEST { + code = 'invisible', + { path = 'a.lua', content = [[ + ---@class A + ---@field package x string + + ---@type A + local obj + + print(obj.x) + ]]}, +} + +TEST { + code = 'invisible', + { path = 'a.lua', content = [[ + ---@class A + ---@field package x string + ]]}, + { path = 'b.lua', content = [[ + ---@type A + local obj + + print(obj.) + ]]} +} + +TEST { + code = 'duplicate-doc-field', + { path = 'a.lua', content = [[ + ---@class A + ---@field number + ]]}, + { path = 'b.lua', content = [[ + ---@class A + ---@field number + ]]} +} + +TEST { + code = 'duplicate-set-field', + { path = 'a.lua', content = [[ + ---@class A + local mt + + function () + end + ]]}, + { path = 'b.lua', content = [[ + ---@class A + local mt + + function () + end + ]]} +} + +TEST { + code = 'duplicate-set-field', + { path = 'a.lua', content = [[ + ---@class A + local mt + + function mt:init() + end + ]]}, + { path = 'b.lua', content = [[ + ---@class B: A + local mt + + function mt:init() + end + ]]} +} diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index e6517aa9b..a18c714b9 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -44,6 +44,7 @@ function TEST(expect) local script, list = catch(file.content, '?') local uri = furi.encode(file.path) files.setText(uri, script) + files.compileState(uri) if #list['?'] > 0 then sourceUri = uri sourcePos = (list['?'][1][1] + list['?'][1][2]) // 2 @@ -172,6 +173,55 @@ TEST { } end +TEST { + { + path = 'a.lua', + content = '---@meta _', + }, + { + path = 'b.lua', + content = 'require ', + }, + hover = [[ +```lua +1 ไธชๅญ—่Š‚ +```]], +} + +TEST { + { + path = 'a.lua', + content = '---@meta xxx', + }, + { + path = 'b.lua', + content = 'require ', + }, + hover = [[ +```lua +1 ไธชๅญ—่Š‚ +```]], +} + +TEST { + { + path = 'a.lua', + content = '---@meta xxx', + }, + { + path = 'b.lua', + content = 'require ', + }, + hover = [=[ +```lua +3 ไธชๅญ—่Š‚ +``` + +--- + +* [[meta]](file:///a.lua)]=], +} + TEST { { path = 'a.lua', @@ -525,7 +575,7 @@ function f(x: string, y: table) @*param* `x` โ€” this is comment -@*param* `y` โ€” comment 1 +@*param* `y` โ€” comment 1 @*return* `name` โ€” comment 2 @@ -696,7 +746,7 @@ function f(a: boolean) --- -@*param* `a` โ€” xxx +@*param* `a` โ€” xxx ```lua a: @@ -1258,7 +1308,7 @@ local n: integer --- - comments]] +comments]] } TEST { @@ -1315,7 +1365,7 @@ local n: integer --- - comments]] +comments]] } TEST { @@ -1334,7 +1384,7 @@ local n: integer --- - comments]] +comments]] } TEST { @@ -1411,7 +1461,7 @@ TEST { --- - comments]] +comments]] } TEST { @@ -1582,3 +1632,223 @@ TEST { } ```]] } + +TEST { + { + path = 'a.lua', + content = [[ + ---@enum(key) + local t = { + x = 1 << 0, + y = 1 << 1, + z = 1 << 2, + } + ]] + }, + hover = [[ +```lua +(enum) A +``` + +--- + +```lua +"x" | "y" | "z" +```]] +} + +TEST { + { + path = 'a.lua', + content = [[ + ---@alias someType + ---| "#" # description + + ---@type someType + local + ]] + }, + hover = [[ +```lua +local someValue: "#" +``` + +--- + +```lua +someType: + | "#" -- description +```]] +} + +TEST { { path = 'a.lua', content = [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +local function (...) +end +]] }, +hover = [[ +```lua +function f(x: number) +``` + +--- + +```lua +function f(x: number, y: number) +```]] +} + +TEST { { path = 'a.lua', content = [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +local function f(...) +end + + +]] }, +hover = [[ +```lua +function f(x: number) +``` + +--- + +```lua +function f(x: number, y: number) +```]] +} + +TEST { { path = 'a.lua', content = [[ +---@overload fun(self: self, x: number) +---@overload fun(self: self, x: number, y: number) +function M:f(...) +end + +M: +]] }, +hover = [[ +```lua +(method) M:f(x: number) +``` + +--- + +```lua +(method) M:f(x: number, y: number) +```]] +} + +TEST { {path = 'a.lua', content = [[ +---@class A + +---@see A # comment1 +local +]]}, +hover = [[ +```lua +local x: unknown +``` + +--- + +See: [A](file:///a.lua#1#10) comment1]] +} + +TEST { {path = 'a.lua', content = [[ +---@class A + +TTT = 1 + +---@see A # comment1 +---@see TTT # comment2 +local +]]}, +hover = [[ +```lua +local x: unknown +``` + +--- + +See: + * [A](file:///a.lua#1#10) comment1 + * [TTT](file:///a.lua#3#0) comment2]] +} + +TEST { {path = 'a.lua', content = [[ +---comment1 +---comment2 +---@overload fun() +---@param x number +local function (x) end +]]}, +hover = [[ +```lua +function f(x: number) +``` + +--- + +comment1 +comment2 + +--- + +```lua +function f() +```]] +} + +TEST { {path = 'a.lua', content = [[ +---"hello world" this is ok +---@param bar any "lorem ipsum" this is ignored +---@param baz any # "dolor sit" this is ignored +local function (bar, baz) +end +]]}, +hover = [[ +```lua +function foo(bar: any, baz: any) +``` + +--- + +"hello world" this is ok + +@*param* `bar` โ€” "lorem ipsum" this is ignored + +@*param* `baz` โ€” "dolor sit" this is ignored]] +} + +TEST { {path = 'a.lua', content = [[ +--comment1 +local x + +--comment2 +x = 1 + +print() +]]}, +hover = [[ +```lua +local x: integer = 1 +``` + +--- + +comment1]] +} + +TEST { {path = 'a.lua', content = [[ +local t = {} + +print(['a b']) +]]}, +hover = [[ +```lua +local t: { + ['a b']: unknown, +} +```]] +} diff --git a/test/crossfile/infer.lua b/test/crossfile/infer.lua new file mode 100644 index 000000000..de29007b0 --- /dev/null +++ b/test/crossfile/infer.lua @@ -0,0 +1,165 @@ +local files = require 'files' +local furi = require 'file-uri' +local vm = require 'vm' +local guide = require 'parser.guide' +local catch = require 'catch' + +rawset(_G, 'TEST', true) + +local function getSource(uri, pos) + local state = files.getState(uri) + if not state then + return + end + local result + guide.eachSourceContain(state.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' + or source.type == 'function' + or source.type == 'table' + or source.type == 'doc.type.name' then + result = source + end + end) + return result +end + +local EXISTS = {} + +local function eq(a, b) + if a == EXISTS and b ~= nil then + return true + end + if b == EXISTS and a ~= nil then + return true + end + local tp1, tp2 = type(a), type(b) + if tp1 ~= tp2 then + return false + end + if tp1 == 'table' then + local mark = {} + for k in pairs(a) do + if not eq(a[k], b[k]) then + return false + end + mark[k] = true + end + for k in pairs(b) do + if not mark[k] then + return false + end + end + return true + end + return a == b +end + +---@diagnostic disable: await-in-sync +function TEST(expect) + local sourcePos, sourceUri + for _, file in ipairs(expect) do + local script, list = catch(file.content, '?') + local uri = furi.encode(file.path) + files.setText(uri, script) + files.compileState(uri) + if #list['?'] > 0 then + sourceUri = uri + sourcePos = (list['?'][1][1] + list['?'][1][2]) // 2 + end + end + + local _ = function () + for _, info in ipairs(expect) do + files.remove(furi.encode(info.path)) + end + end + + local source = getSource(sourceUri, sourcePos) + assert(source) + local view = vm.getInfer(source):view(sourceUri) + assert(eq(view, expect.infer)) +end + +TEST { + { + path = 'a.lua', + content = [[ +---@class T +local x + +---@class V +x.y = 1 +]], + }, + { + path = 'b.lua', + content = [[ +---@type T +local x + +if x.y then + print(x.) +end + ]], + }, + infer = 'V', +} + +TEST { + { path = 'a.lua', content = [[ +X = 1 +X = true +]], }, + { path = 'b.lua', content = [[ +print() +]], }, + infer = 'integer', +} + +TEST { + { path = 'a.lua', content = [[ +---@meta +X = 1 +X = true +]], }, + { path = 'b.lua', content = [[ +print() +]], }, + infer = 'boolean|integer', +} + +TEST { + { path = 'a.lua', content = [[ +return 1337, "string", true +]], }, + { path = 'b.lua', content = [[ +local , b, c = require 'a +]], }, + infer = 'integer', +} + +TEST { + { path = 'a.lua', content = [[ +return 1337, "string", true +]], }, + { path = 'b.lua', content = [[ +local a, , c = require 'a +]], }, + infer = 'unknown', +} + +TEST { + { path = 'a.lua', content = [[ +return 1337, "string", true +]], }, + { path = 'b.lua', content = [[ +local a, b, = require 'a +]], }, + infer = 'nil', +} diff --git a/test/crossfile/init.lua b/test/crossfile/init.lua index aec9a0447..d4cef46a2 100644 --- a/test/crossfile/init.lua +++ b/test/crossfile/init.lua @@ -1,4 +1,5 @@ require 'crossfile.definition' +require 'crossfile.infer' require 'crossfile.references' require 'crossfile.hover' require 'crossfile.completion' diff --git a/test/crossfile/references.lua b/test/crossfile/references.lua index 1a9f25088..36c081708 100644 --- a/test/crossfile/references.lua +++ b/test/crossfile/references.lua @@ -58,7 +58,7 @@ function TEST(datas) local sourceList local sourceUri for i, data in ipairs(datas) do - local uri = furi.encode(data.path) + local uri = furi.encode(TESTROOT .. data.path) local newScript, catched = catch(data.content, '!?~') if catched['!'] or catched['~'] then for _, position in ipairs(catched['!'] + catched['~']) do @@ -74,16 +74,17 @@ function TEST(datas) sourceUri = uri end files.setText(uri, newScript) + files.compileState(uri) end local _ = function () for _, info in ipairs(datas) do - files.remove(furi.encode(info.path)) + files.remove(furi.encode(TESTROOT .. info.path)) end end local sourcePos = (sourceList[1][1] + sourceList[1][2]) // 2 - local positions = core(sourceUri, sourcePos) + local positions = core(sourceUri, sourcePos, true) if positions then local result = {} for i, position in ipairs(positions) do @@ -361,15 +362,30 @@ TEST { { path = 'a.lua', content = [[ - local <~t~> = require 'b' - return + local <~x~> = require 'b' + return ]] }, { path = 'b.lua', content = [[ - local t = require 'a' - return t + local y = require 'a' + return y ]] }, } + +TEST { + { + path = 'a.lua', + content = [[ + ---@alias <~XX~> number + ]] + }, + { + path = 'b.lua', + content = [[ + ---@type + ]] + } +} diff --git a/test/definition/bug.lua b/test/definition/bug.lua index b564f7646..2e2a59724 100644 --- a/test/definition/bug.lua +++ b/test/definition/bug.lua @@ -46,6 +46,13 @@ TEST [[ obj[#+1] = {} ]] +TEST [[ +self = { + = {} +} +self[self.] = lbl +]] + TEST [[ self = { results = { diff --git a/test/definition/init.lua b/test/definition/init.lua index 3badaadfd..eb6f14351 100644 --- a/test/definition/init.lua +++ b/test/definition/init.lua @@ -21,17 +21,18 @@ local function founded(targets, results) return true end +---@async function TEST(script) local newScript, catched = catch(script, '!?') - files.setText('', newScript) + files.setText(TESTURI, newScript) - local results = core('', catched['?'][1][1]) + local results = core(TESTURI, catched['?'][1][1]) if results then local positions = {} for i, result in ipairs(results) do if not vm.isMetaFile(result.uri) then - positions[i] = { result.target.start, result.target.finish } + positions[#positions+1] = { result.target.start, result.target.finish } end end assert(founded(catched['!'], positions)) @@ -39,7 +40,7 @@ function TEST(script) assert(#catched['!'] == 0) end - files.remove('') + files.remove(TESTURI) end require 'definition.local' diff --git a/test/definition/luadoc.lua b/test/definition/luadoc.lua index afa532277..7a460593a 100644 --- a/test/definition/luadoc.lua +++ b/test/definition/luadoc.lua @@ -207,23 +207,30 @@ y. ]] TEST [[ ----@class -local unit!> +---@class +local mt -function unit:pants() +function mt:f() end ----@see +---@see ]] TEST [[ ----@class loli -local unit +---@class A +local mt -function unit:() +function () end ----@see loli# +---@see +]] + +TEST [[ +AAA = {} + = 1 + +---@see ]] TEST [[ @@ -297,6 +304,21 @@ local v1 = Generic(Foo) print(v1.) ]] + +TEST [[ +---@class n.Foo +local Foo = {} +function Foo:bar1() end + +---@generic T +---@param arg1 n.`T` +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic(Foo) +print(v1.) +]] + TEST [[ ---@class Foo local Foo = {} @@ -311,6 +333,21 @@ local v1 = Generic("Foo") print(v1.) ]] + +TEST [[ +---@class n.Foo +local Foo = {} +function Foo:() end + +---@generic T +---@param arg1 n.`T` +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic("Foo") +print(v1.) +]] + TEST [[ ---@class A local t @@ -635,7 +672,7 @@ end ]] TEST [[ ----@class TT: { } +---@class TT: { : V } ---@type TT local t @@ -646,7 +683,7 @@ print(t.) ]] TEST [[ ----@alias TT { } +---@alias TT { : V } ---@type TT local t @@ -816,7 +853,7 @@ z. ]] TEST [[ ----@type { , y: number } +---@type { : number, y: number } local t print(t.) @@ -839,14 +876,14 @@ local !> = t[1] TEST [[ ---@class A ----@field ? +---@field []? local t print(t.) ]] TEST [[ ----@type { } +---@type { []?: boolean } local t print(t.) @@ -922,4 +959,61 @@ f { } ]] +TEST [[ +---@class A +local a +a.__index = a + +---@class B: A +local b +b.!> = b +]] + +TEST [[ +---@class myClass +local myClass = { nested = {} } + +function myClass.nested.() end + +---@type myClass +local class + +class.nested.() +]] + +TEST [[ +---@class myClass +local myClass = { has = { nested = {} } } + +function myClass.has.nested.() end + +---@type myClass +local class + +class.has.nested.() +]] + +TEST [[ +---@type table +local x = { + = 1, + b = 2, + c = 3 +} + +print(x.) +]] + config.set(nil, 'Lua.type.castNumberToInteger', true) + +TEST [[ +---@class + +---@class !> +]] + +TEST [[ +---@alias number + +---@alias !> number +]] diff --git a/test/diagnostics/ambiguity-1.lua b/test/diagnostics/ambiguity-1.lua new file mode 100644 index 000000000..6b8e41da9 --- /dev/null +++ b/test/diagnostics/ambiguity-1.lua @@ -0,0 +1,29 @@ +TEST [[ +local x +x = +]] + +TEST [[ +local x, y +x = +]] + +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 +]] diff --git a/test/diagnostics/assign-type-mismatch.lua b/test/diagnostics/assign-type-mismatch.lua new file mode 100644 index 000000000..dc55a7dac --- /dev/null +++ b/test/diagnostics/assign-type-mismatch.lua @@ -0,0 +1,481 @@ +local config = require 'config' + +TEST [[ +local m = {} + +---@type integer[] +m.ints = {} +]] + +TEST [[ +---@class A +---@field x A + +---@type A +local t + +t.x = {} +]] + +TEST [[ +---@class A +---@field x integer + +---@type A +local t + + = true +]] + +TEST [[ +---@class A +---@field x integer + +---@type A +local t + +---@type boolean +local y + + = y +]] + +TEST [[ +---@class A +local m + +m.x = 1 + +---@type A +local t + + = true +]] + +TEST [[ +---@class A +local m + +---@type integer +m.x = 1 + + = true +]] + +TEST [[ +---@class A +local mt + +---@type integer +mt.x = 1 + +function mt:init() + = true +end +]] + +TEST [[ +---@class A +---@field x integer + +---@type A +local t = { + = true +} +]] + +TEST [[ +---@type boolean[] +local t = {} + +t[5] = nil +]] + +TEST [[ +---@type table +local t = {} + +t['x'] = nil +]] + +TEST [[ +---@type [boolean] +local t = { = nil } + +t = nil +]] + +TEST [[ +local t = { true } + +t[1] = nil +]] + +TEST [[ +---@class A +local t = { + x = 1 +} + + = true +]] + +TEST [[ +---@type number +local t + +t = 1 +]] + +TEST [[ +---@type number +local t + +---@type integer +local y + +t = y +]] + +TEST [[ +---@class A +local m + +---@type number +m.x = 1 + + = {} +]] + +TEST [[ +local n + +if G then + n = {} +else + n = nil +end + +local t = { + x = n, +} +]] + +TEST [[ +---@type boolean[] +local t = {} + +---@type boolean? +local x + +t[#t+1] = x +]] + +TEST [[ +---@type number +local n +---@type integer +local i + + = n +]] + +config.set(nil, 'Lua.type.castNumberToInteger', true) +TEST [[ +---@type number +local n +---@type integer +local i + +i = n +]] + +config.set(nil, 'Lua.type.castNumberToInteger', false) +TEST [[ +---@type number|boolean +local nb + +---@type number +local n + + = nb +]] + +config.set(nil, 'Lua.type.weakUnionCheck', true) +TEST [[ +---@type number|boolean +local nb + +---@type number +local n + +n = nb +]] + +config.set(nil, 'Lua.type.weakUnionCheck', false) +TEST [[ +---@class Option: string + +---@param x Option +local function f(x) end + +---@type Option +local x = 'aaa' + +f(x) +]] +config.set(nil, 'Lua.type.weakUnionCheck', true) + +TEST [[ +---@type number +local = 'aaa' +]] +TEST [[ +---@class X + +---@class A +local mt = G + +---@type X +mt._x = nil +]] +config.set(nil, 'Lua.type.weakUnionCheck', false) + +config.set(nil, 'Lua.type.weakNilCheck', true) +TEST [[ +---@type number? +local nb + +---@type number +local n + +n = nb +]] + +TEST [[ +---@type number|nil +local nb + +---@type number +local n + +n = nb +]] +config.set(nil, 'Lua.type.weakNilCheck', false) + +TEST [[ +---@class A +local a = {} + +---@class B +local = a +]] + +TEST [[ +---@class A +local a = {} + +---@class B: A +local b = a +]] + +TEST [[ +---@class A +local a = {} +a.__index = a + +---@class B: A +local b = setmetatable({}, a) +]] + +TEST [[ +---@class A +local a = {} + +---@class B: A +local b = setmetatable({}, {__index = a}) +]] + +TEST [[ +---@class A +local a = {} + +---@class B +local = setmetatable({}, {__index = a}) +]] + +TEST [[ +---@class A +---@field x number? +local a + +---@class B +---@field x number +local b + +b.x = a.x +]] + +TEST [[ + +---@class A +---@field x number? +local a + +---@type number +local t + +t = a.x +]] + +TEST [[ +local mt = {} +mt.x = 1 +mt.x = nil +]] + +config.set(nil, 'Lua.type.weakUnionCheck', true) +TEST [[ +---@type number +local x = G +]] + +TEST [[ +---@generic T +---@param x T +---@return T +local function f(x) + return x +end +]] +config.set(nil, 'Lua.type.weakUnionCheck', false) + + +TEST [[ +---@alias test boolean + +---@type test +local = 4 +]] + +TEST [[ +---@class MyClass +local MyClass = {} + +function MyClass:new() + ---@class MyClass + local myObject = setmetatable({ + initialField = true + }, self) + + print(myObject.initialField) +end +]] + +TEST [[ +---@class T +local t = { + x = nil +} + +t.x = 1 +]] + +TEST [[ +---@type {[1]: string, [10]: number, xx: boolean} +local t = { + , + = 's', + = 1, +} +]] + +TEST [[ +---@type boolean[] +local t = { , , } +]] + +TEST [[ +---@type boolean[] +local t = { true, false, nil } +]] + +TEST [[ +---@type boolean|nil +local x + +---@type boolean[] +local t = { true, false, x } +]] + +TEST [[ +---@enum Enum +local t = { + x = 1, + y = 2, +} + +---@type Enum +local y + +---@type integer +local x = y +]] + +TEST [[ +---@type string|string[]|string[][] +local t = {{'a'}} +]] + +TEST [[ +local A = "Hello" +local B = "World" + +---@alias myLiteralAliases `A` | `B` + +---@type myLiteralAliases +local x = A +]] + +TEST [[ +local enum = { a = 1, b = 2 } + +---@type { [integer] : boolean } +local t = { + = 1, + = 2, + = 3, +} +]] + +TEST [[ +---@class SomeClass +---@field [1] string +-- ... + +---@param some_param SomeClass|SomeClass[] +local function some_fn(some_param) return end + +some_fn { { "test" } } -- <- diagnostic: "Cannot assign `table` to `string`." +]] + +TEST [[ +---@type string[] +local arr = { + , +} +]] + +TEST [[ +---@type (string|boolean)[] +local arr2 = { + , -- no warnings +} +]] + +TEST [[ +local t = {} +t.a = 1 +t.a = 2 +return t +]] diff --git a/test/diagnostics/await-in-sync.lua b/test/diagnostics/await-in-sync.lua new file mode 100644 index 000000000..323c11137 --- /dev/null +++ b/test/diagnostics/await-in-sync.lua @@ -0,0 +1,132 @@ +TEST [[ +function F() + () +end +]] + +TEST [[ +---@async +function F() + coroutine.yield() +end +]] + +TEST [[ +---@type async fun() +local f + +function F() + () +end +]] + +TEST [[ +---@type async fun() +local f + +---@async +function F() + f() +end +]] + +TEST [[ +local function f(cb) + cb() +end + +return function() + (function () ---@async + return nil + end) +end +]] + +TEST [[ +local function f(cb) + pcall(cb) +end + +return function() + (function () ---@async + return nil + end) +end +]] + +TEST [[ +---@param c any +local function f(c) + return c +end + +return function () + f(function () ---@async + return nil + end) +end +]] + +TEST [[ +---@param ... any +local function f(...) + return ... +end + +return function () + f(function () ---@async + return nil + end) +end +]] + +TEST [[ +---@vararg any +local function f(...) + return ... +end + +return function () + f(function () ---@async + return nil + end) +end +]] + +TEST [[ +local function f(...) + return ... +end + +return function () + f(function () ---@async + return nil + end) +end +]] + +TEST [[ +local function f(...) + return ... +end + +return function () + f(function () ---@async + return nil + end) +end +]] + +TEST [[ +local function f(cb) + cb() +end + +local function af() + (function () ---@async + return nil + end) +end + +return af +]] diff --git a/test/diagnostics/cast-local-type.lua b/test/diagnostics/cast-local-type.lua new file mode 100644 index 000000000..f79bf48d1 --- /dev/null +++ b/test/diagnostics/cast-local-type.lua @@ -0,0 +1,334 @@ +TEST [[ +local x = 0 + + = true +]] + +TEST [[ +---@type integer +local x + + = true +]] + +TEST [[ +---@type unknown +local x + +x = nil +]] + +TEST [[ +---@type unknown +local x + +x = 1 +]] + +TEST [[ +---@type unknown|nil +local x + +x = 1 +]] + +TEST [[ +local x = {} + +x = nil +]] + +TEST [[ +---@type string +local x + + = nil +]] + +TEST [[ +---@type string? +local x + +x = nil +]] + +TEST [[ +---@type table +local x + + = nil +]] + +TEST [[ +local x + +x = nil +]] + +TEST [[ +---@type integer +local x + +---@type number + = f() +]] + +TEST [[ +---@type number +local x + +---@type integer +x = f() +]] + +TEST [[ +---@type number|boolean +local x + +---@type string + = f() +]] + +TEST [[ +---@type number|boolean +local x + +---@type boolean +x = f() +]] + +TEST [[ +---@type number|boolean +local x + +---@type boolean|string + = f() +]] + +TEST [[ +---@type boolean +local x + +if not x then + return +end + +x = f() +]] + +TEST [[ +---@type boolean +local x + +---@type integer +local y + + = y +]] + +TEST [[ +local y = true + +local x +x = 1 +x = y +]] + +TEST [[ +local t = {} + +local x = 0 +x = x + #t +]] + +TEST [[ +local x = 0 + +x = 1.0 +]] + +TEST [[ +---@class A + +local t = {} + +---@type A +local a + +t = a +]] + +TEST [[ +---@type integer +local x + +x = 1.0 +]] + +TEST [[ +---@type integer +local x + + = 1.5 +]] + +TEST [[ +---@type integer +local x + +x = 1 + G +]] + +TEST [[ +---@type integer +local x + +x = 1 + G +]] + +TEST [[ +---@alias A integer + +---@type A +local a + +---@type integer +local b + +b = a +]] + +TEST [[ +---@type string[] +local t + + = 'xxx' +]] + +TEST [[ +---@type 1|2 +local x + +x = 1 +x = 2 + = 3 +]] + +TEST [[ +---@type 'x'|'y' +local x + +x = 'x' +x = 'y' + = 'z' +]] + +TEST [[ +local t = { + x = 1, +} + +local x +t[x] = true +]] + +TEST [[ +---@type table +local x + +---@type table +local y + + = y +]] + +TEST [[ +---@type table +local x + +---@type table +local y + + = y +]] + +TEST [[ +---@type table +local x + +---@type table +local y + +x = y +]] + +TEST [[ +---@type { x: number, y: number } +local t1 + +---@type { x: number } +local t2 + + = t2 +]] + +TEST [[ +---@type { x: number, [integer]: number } +local t1 + +---@type { x: number } +local t2 + + = t2 +]] + +TEST [[ +local x + +if X then + x = 'A' +elseif X then + x = 'B' +else + x = 'C' +end + +local y = x + + = nil +]] +(function (diags) + local diag = diags[1] + assert(diag.message == [[ +ๅทฒๆ˜พๅผๅฎšไน‰ๅ˜้‡็š„็ฑปๅž‹ไธบ `string` ๏ผŒไธ่ƒฝๅ†ๅฐ†ๅ…ถ็ฑปๅž‹่ฝฌๆขไธบ `nil`ใ€‚ +- `nil` ๆ— ๆณ•ๅŒน้… `string` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `string`]]) +end) + +TEST [[ +---@type 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z' +local x + + = nil +]] +(function (diags) + local diag = diags[1] + assert(diag.message == [[ +ๅทฒๆ˜พๅผๅฎšไน‰ๅ˜้‡็š„็ฑปๅž‹ไธบ `'A'|'B'|'C'|'D'|'E'...(+21)` ๏ผŒไธ่ƒฝๅ†ๅฐ†ๅ…ถ็ฑปๅž‹่ฝฌๆขไธบ `nil`ใ€‚ +- `nil` ๆ— ๆณ•ๅŒน้… `'A'|'B'|'C'|'D'|'E'...(+21)` +- `nil` ๆ— ๆณ•ๅŒน้… `'A'|'B'|'C'|'D'|'E'...(+21)` ไธญ็š„ไปปไฝ•ๅญ็ฑป +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'Z'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'Y'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'X'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'W'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'V'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'U'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'T'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'S'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'R'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'Q'` +...(+13) +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'C'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'B'` +- ็ฑปๅž‹ `nil` ๆ— ๆณ•ๅŒน้… `'A'`]]) +end) diff --git a/test/diagnostics/cast-type-mismatch.lua b/test/diagnostics/cast-type-mismatch.lua new file mode 100644 index 000000000..9fba58f69 --- /dev/null +++ b/test/diagnostics/cast-type-mismatch.lua @@ -0,0 +1,13 @@ +TEST [[ +---@type string|boolean +local t + +---@cast t string +]] + +TEST [[ +---@type string|boolean +local t + +---@cast t +]] diff --git a/test/diagnostics/circle-doc-class.lua b/test/diagnostics/circle-doc-class.lua new file mode 100644 index 000000000..089b0c9b9 --- /dev/null +++ b/test/diagnostics/circle-doc-class.lua @@ -0,0 +1,14 @@ +TEST [[ +---@class +---@class +---@class +---@class +]] + +TEST [[ +---@class A : B +---@class B : C +---@class C : D +---@class D +]] + diff --git a/test/diagnostics/close-non-object.lua b/test/diagnostics/close-non-object.lua new file mode 100644 index 000000000..11b882b79 --- /dev/null +++ b/test/diagnostics/close-non-object.lua @@ -0,0 +1,18 @@ +TEST [[ +local _ = +]] + +TEST [[ +local _ = +]] + +TEST [[ +local c = +]] + +TEST [[ +---@type unknown +local t + +local _ = t +]] diff --git a/test/diagnostics/code-after-break.lua b/test/diagnostics/code-after-break.lua new file mode 100644 index 000000000..a150948ba --- /dev/null +++ b/test/diagnostics/code-after-break.lua @@ -0,0 +1,7 @@ +TEST [[ +while true do + break + +end +]] diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua deleted file mode 100644 index e10ac52cc..000000000 --- a/test/diagnostics/common.lua +++ /dev/null @@ -1,2069 +0,0 @@ -local config = require 'config' -local util = require 'utility' - -local disables = config.get(nil, 'Lua.diagnostics.disable') - -TEST [[ -local -]] - -TEST [[ -local y -local x = y -]] - -TEST [[ -local function x() -end -x() -]] - -TEST [[ -return function (x) - x.a = 1 -end -]] - -TEST [[ -local = {} -.a = 1 -]] - -TEST [[ -local () -end!> -]] - - -TEST [[ -local = -]] - -TEST [[ -local - = -]] - -TEST [[ -local -local () - x() -end!> -]] - -TEST [[ -local print, _G -print() -print() -print() -print() -print() -print(Z) -print(_G) -Z = 1 -]] - -TEST [[ -:::: -]] - -TEST [[ - -]] - -TEST [[ - - -]] - -TEST [[ -X = 1 -]] - -TEST [[ -X = [=[ - ]=] -]] - -TEST [[ --- xxxx -]] - -TEST [[ --- [=[ - ]=] -]] - -TEST [[ -local x -print(x) -local -print(x) -]] - -TEST [[ -local x -print(x) -local -print(x) -local -print(x) -]] - -TEST [[ -local _ -print(_) -local _ -print(_) -local _ENV -(_ENV) -- ็”ฑไบŽ้‡ๅฎšไน‰ไบ†_ENV๏ผŒๅ› ๆญคprintๅ˜ไธบไบ†ๆœชๅฎšไน‰ๅ…จๅฑ€ๅ˜้‡ -]] - -TEST [[ -local x -return x, function () - return x -end -]] - -TEST [[ -print(1) -_ENV = nil -]] - -TEST [[ ----@diagnostic disable: undefined-global -_ENV = nil -() -- `print` and `A` should warning -]] - -TEST [[ ----@diagnostic disable: undefined-global -local _ENV = nil -() -- `print` and `A` should warning -]] - -TEST [[ -_ENV = {} -print(A) -- no warning -]] - -TEST [[ -local _ENV = {} -print(A) -- no warning -]] - -TEST [[ ----@type iolib -_ENV = {} -(stderr) -- `print` is warning but `stderr` is not -]] - -TEST [[ ----@type iolib -local _ENV = {} -(stderr) -- `print` is warning but `stderr` is not -]] - -TEST [[ -local _ENV = { print = print } -print(1) -]] - -util.arrayInsert(disables, 'undefined-env-child') -TEST [[ -_ENV = nil - = 1 --> _ENV.GLOBAL = 1 -]] - -TEST [[ -_ENV = nil -local _ = --> local _ = _ENV.print -]] - -TEST [[ -_ENV = {} -GLOBAL = 1 --> _ENV.GLOBAL = 1 -]] - -TEST [[ -_ENV = {} -local _ = print --> local _ = _ENV.print -]] - -TEST [[ -GLOBAL = 1 -_ENV = nil -]] - -util.arrayRemove(disables, 'undefined-env-child') -TEST [[ -:sub(1, 1) -]] - -TEST [[ -print() -('string') -]] - -TEST [[ -print -{} -{} -]] - -TEST [[ -local x -return x - : f(1) - : f(1) -]] - -TEST [[ -return { - -} -]] - -TEST [[ -return { - -} -]] - -TEST [[ -print() -'string' -]] - -TEST [[ -print -{ - x = 1, -} -]] - -TEST [[ -local function x(a, b) - return a, b -end -x(1, 2, ) -]] - -TEST [[ -local function x(a, b, ...) - return a, b, ... -end -x(1, 2, 3, 4, 5) -]] - -TEST [[ ----@type fun(a, b, ...) -local x -x(1, 2, 3, 4, 5) -]] - -TEST [[ -local m = {} -function m:x(a, b) - return a, b -end -m:x(1, 2, ) -]] - -TEST [[ -local m = {} -function m:x(a, b) - return a, b -end -m.x(m, 2, 3, ) -]] - -TEST [[ -local m = {} -function m.x(a, b) - return a, b -end -m:x(1, , , ) -]] - -TEST [[ -local function x(a, b) - return a, b -end -x(1) -]] - -TEST [[ ----@param a integer ----@param b integer -local function x(a, b) - return a, b -end - -]] - -TEST [[ ----@param a integer ----@param b integer -local function x(a, b) - return a, b -end - -]] - -TEST [[ ----@param a integer ----@param b integer ----@param ... integer -local function x(a, b, ...) - return a, b, ... -end -x(1, 2) -]] - -TEST [[ ----@diagnostic disable: unused-local - ----@param a integer ----@param b integer -local function f(a, b) -end - -f(...) -]] - -TEST [[ ----@diagnostic disable: unused-local - ----@param a integer ----@param b integer -local function f(a, b) -end - -local function return2Numbers() - return 1, 2 -end - -f(return2Numbers()) -]] - -TEST [[ ----@param a integer ----@param b? integer -local function x(a, b) - return a, b -end -x(1) -]] - -TEST [[ ----@param b integer? -local function x(a, b) - return a, b -end -x(1) -]] - -TEST [[ ----@param b integer|nil -local function x(a, b) - return a, b -end -x(1) -]] - -TEST [[ -local m = {} -function m.x() -end -m:x() -]] - -TEST [[ -InstanceName = 1 -Instance = _G[InstanceName] -]] - -TEST [[ -local _ = (''):sub(1, 2) -]] - -TEST [=[ -return [[ - -]] -]=] - -util.arrayInsert(disables, 'close-non-object') -TEST [[ -local _ = function () end -]] -util.arrayRemove(disables, 'close-non-object') - -TEST [[ -local _ = -]] - -TEST [[ -local _ = -]] - -TEST [[ -local c = -]] - -util.arrayInsert(disables, 'unused-local') -TEST [[ -local f = -]] - -TEST [[ -local f;f = -]] - -TEST [[ -local -]] - -TEST [[ -local -]] - - -TEST [[ -local - -local -]] - -util.arrayRemove(disables, 'unused-local') -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 = {} -function :f() -end -]] - -TEST [[ -local = {} -.a = 1 -]] - -TEST [[ -local = {} -['a'] = 1 -]] - -TEST [[ -local function f() - return 'something' -end -f() -]] - -TEST [[ -local function f() - return 'something' -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, , ) -]] - -TEST [[ -local mt = {} -function mt:f(a, b) - return a, b -end -mt.f(mt, 2, 3, ) -]] - - -TEST [[ -local mt = {} -function mt.f(a, b) - return a, b -end -mt:f(1, , , ) -]] - -TEST [[ -local mt = {} -function mt:f(a, b) - return a, b -end -mt:f(1, 2, , ) -]] - -TEST [[ -local function f(a, b, ...) - return a, b, ... -end -f(1, 2, 3, 4) -]] - -TEST [[ -local _ = next({}, 1, ) -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() ---tostring = realTostring ---tostring(1) ---]] - -TEST [[ - = 1 -tostring = 1 -ROOT = 1 -_G.bb = 1 -]] - -TEST [[ -local f = load('') -if f then - f(1, 2, 3) -end -]] - -TEST [[ -local _ = -]] - -TEST [[ -X = table[] -]] - -TEST [[ -return { - = 1, - y = 2, - = 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(m) -]] - -TEST [[ - -]] - -TEST [[ - -]] - -TEST [[ -if true then -else - return -end -]] - -TEST [[ -while true do -end -]] - -TEST [[ - -]] - -TEST [[ - -]] - -TEST [[ -local _ = 1, -]] - -TEST [[ -_ = 1, -]] - -TEST [[ -function X() - do - local k - print(k) - end - local k = 1 - print(k) -end -]] - -TEST [[ -function X() - local loc - print(loc) -end -]] - -TEST [[ -local = {} -[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 = -]] - -TEST [[ -local x, y -x = -]] - -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, ) -]] - -TEST [[ -while true do - break - -end -]] - -TEST [[ -local x, , = 1 -print(x, y, z) -]] - -TEST [[ -local x, y, = 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, , = 1 -print(x, y, z) -]] - -TEST [[ -X, , = 1 -]] - -TEST [[ -T = {} -T.x, , = 1 -]] - -TEST [[ -T = {} -T['x'], , = 1 -]] - ---TEST [[ ------@class ------@class ---]] - -TEST [[ ----@alias integer ----@alias integer -]] - -TEST [[ ----@class A : -]] - -TEST [[ ----@class ----@class ----@class ----@class -]] - -TEST [[ ----@class A : B ----@class B : C ----@class C : D ----@class D -]] - -TEST [[ ----@type -]] - -TEST [[ ----@class A ----@type A|| -]] - -TEST [[ ----@class AAA ----@alias B AAA - ----@type B -]] - -TEST [[ ----@alias B -]] - -TEST [[ ----@class A ----@class B ----@alias B -]] - -TEST [[ ----@param -]] - -TEST [[ ----@class Class ----@param Class -local function f(x) - return x -end -f() -]] - -TEST [[ ----@class Class ----@param Class -function F(x) - return x -end -F() -]] - -TEST [[ ----@class Class ----@param Class ----@param y Class ----@param Class -local function f(x, y) - return x, y -end - -local _ -f(_, _) -]] - -TEST [[ ----@field ----@class Class -]] - -TEST [[ ----@class Class - ----@field -]] - -TEST [[ ----@class Class ---- ----@field x Class -]] - -TEST [[ ----@class Class ----@field x Class ----@field Class -]] - -TEST [[ ----@class Class : any -]] - -TEST [[ ----@type fun(a: integer) -local f -f(1) -]] - -TEST [[ ----@class c -c = {} -]] - -TEST [[ ----@generic T: any ----@param v T ----@param message any ----@return T ----@return any message -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. + 1) -print(v:method1()) -print(v.method2()) -print(v:()) - ----@type Bar -local v2 -print(v2.field1 + 1) -print(v2.field2 + 1) -print(v2. + 1) -print(v2.field4 + 1) -print(v2:method1()) -print(v2.method2()) -print(v2:()) - -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:() -- 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:() -- doc.class.name -v:method3() -]] - --- checkUndefinedField ็ฑปๅๅ’Œ็ฑปๅ˜้‡ๅŒๅ๏ผŒ็ฑปๅ˜้‡่ขซ็›ดๆŽฅไฝฟ็”จ -TEST [[ ----@class Foo -local Foo -function Foo:method1() end -Foo:() -- doc.class -Foo:() -- 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.() -- doc.class - self:method1() - return self.() -- 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 - = v -v2:method1() -v2:() -]] - -TEST [[ ----@type table -T1 = {} -print(T1.f1) ----@type tablelib -T2 = {} -print(T2.) -]] ---]==] -TEST [[ ----@overload fun(...) -local function f() end - -f(1) -]] - -TEST [[ -for i = do - print(i) -end -]] - -TEST [[ -for i = do - print(i) -end -]] - -TEST [[ -for i = do - print(i) -end -]] - -TEST [[ -for i = do - print(i) -end -]] - -TEST [[ -for i = 1, 1 do - print(i) -end -]] - -TEST [[ -local m = {} - -function () -end - -function () -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 - = 1 -]] - -TEST [[ ----@diagnostic disable -x = 1 -]] - -TEST [[ ----@diagnostic disable ----@diagnostic enable - = 1 -]] - -TEST [[ ----@diagnostic disable ----@diagnostic disable ----@diagnostic enable -x = 1 -]] - -TEST [[ ----@diagnostic disable-next-line: -]] - -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, , 3, - [] = 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 fun(eventName: '"died"', cb: fun(i: integer)) -local emit = {} -]] - --- redundant-return -TEST [[ -local function f() - -end -f() -]] - -TEST [[ -local function f() - return nil -end -f() -]] - -TEST [[ -local function f() - local function x() - - 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() - () -end -]] - -TEST [[ ----@async -function F() - coroutine.yield() -end -]] - -TEST [[ ----@type async fun() -local f - -function F() - () -end -]] - -TEST [[ ----@type async fun() -local f - ----@async -function F() - f() -end -]] - -TEST [[ -local function f(cb) - cb() -end - -(function () ---@async - return nil -end) -]] - -TEST [[ -local function f(cb) - pcall(cb) -end - -(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 - - -]] - -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() -]] - -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() - (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 - -return { - x = 1, -} -]] - -TEST [[ ----@class A #1 -]] - -TEST [[ ----@class A 1 -]] - -TEST [[ -return ('1'):upper() -]] - -TEST [[ -local value -value = '1' -value = value:upper() -]] - -TEST [[ -T = {} ----@deprecated # comment -T.x = 1 - -print() -]] - -TEST [[ -T = {} - ----@deprecated -function T:ff() -end - -() -]] - -TEST [[ ----@type string? -local x - -S = :upper() -]] - -TEST [[ ----@type string? -local x - -if x then - S = x:upper() -end -]] - -TEST [[ ----@type string? -local x - -if not x then - x = '' -end - -S = x:upper() -]] - -TEST [[ ----@type fun()? -local x - -S = () -]] - -TEST [[ ----@type integer? -local x - -T = {} -T[] = 1 -]] - -TEST [[ -local x, y -local z = x and y - -print(z.y) -]] - -TEST [[ -local x, y -function x() - y() -end - -function y() - x() -end - -x() -]] - -TEST [[ ----@meta - ----@param x fun() -local function f1(x) -end - ----@return fun() -local function f2() -end - -f1(f2()) -]] - -TEST [[ ----@meta - ----@type fun():integer -local f - ----@param x integer -local function foo(x) end - -foo(f()) -]] - -TEST [[ ----@type string|table -local n - -print(n.x) -]] - -TEST [[ ----@diagnostic disable: unused-local, unused-function, undefined-global - -function F() end - ----@param x boolean -function F(x) end - -F(k()) -]] - -TEST [[ -local function f() - return 1, 2, 3 -end - -local function k() -end - -k() -]] - -TEST [[ ----@diagnostic disable: unused-local -local function f() - return 1, 2, 3 -end - ----@param x integer -local function k(x) -end - -k(f()) -]] - -TEST [[ ----@cast integer -]] - -TEST [[ ----@diagnostic disable: unused-local -local x, y ----@cast y number -]] - -TEST [[ ----@class A - ----@class B ----@field [integer] A ----@field [A] true -]] - -TEST [[ ----@class A - ----@class B ----@field [A] A ----@field [] true -]] - -TEST [[ ----@diagnostic disable: unused-local - ----@type 'x' -local t - -local n = t:upper() -]] - -TEST [[ ----@diagnostic disable: unused-local - ----@alias A 'x' - ----@type A -local t - -local n = t:upper() -]] - -TEST [[ -local t = {} - -function t:init() end - - -]] - -TEST [[ -return function f(x, y, z) end -]] - -util.arrayInsert(disables, 'redundant-return') -TEST [[ ----@return number -function F() - -end -]] - -TEST [[ ----@return number, number -function F() - 1 -end -]] - -TEST [[ ----@return number, number? -function F() - return 1 -end -]] - -TEST [[ ----@return ... -function F() - return -end -]] - -TEST [[ ----@return number, number? -function F() - return 1, 1, -end -]] - -TEST [[ ----@return number, number? -function F() - return 1, 1, , , -end -]] - -TEST [[ ----@return number, number -local function r2() end - ----@return number, number? -function F() - return 1, -end -]] - -TEST [[ ----@return number -function F() - X = 1 -end -]] - -TEST [[ -local A ----@return number -function F() - if A then - return 1 - end -end -]] - -TEST [[ -local A, B ----@return number -function F() - if A then - return 1 - elseif B then - return 2 - end -end -]] - -TEST [[ -local A, B ----@return number -function F() - if A then - return 1 - elseif B then - return 2 - else - return 3 - end -end -]] - -TEST [[ -local A, B ----@return number -function F() - if A then - elseif B then - return 2 - else - return 3 - end -end -]] - -TEST [[ ----@return any -function F() - X = 1 -end -]] - -TEST [[ ----@return any, number -function F() - X = 1 -end -]] - -TEST [[ ----@return number, any -function F() - X = 1 -end -]] - -TEST [[ ----@return any, any -function F() - X = 1 -end -]] - -TEST [[ -local A ----@return number -function F() - for _ = 1, 10 do - if A then - return 1 - end - end - error('should not be here') -end -]] - -TEST [[ -local A ----@return number -function F() - while true do - if A then - return 1 - end - end -end -]] - -TEST [[ -local A ----@return number -function F() - while A do - if A then - return 1 - end - end -end -]] - -TEST [[ -local A ----@return number -function F() - while A do - if A then - return 1 - else - return 2 - end - end -end -]] - -TEST [[ ----@return number? -function F() - -end -]] - -util.arrayRemove(disables, 'redundant-return') - -TEST [[ ----@class A ----@operator : A -]] - -config.add(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*') - -TEST [[ -local -local ll_1 -local ll_2 -local -]] - -config.remove(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*') - -TEST [[ ----@diagnostic disable: undefined-global - -if X then - return false -elseif X then - return false -else - return false -end - -]] - -TEST [[ ----@diagnostic disable: undefined-global - -function X() - if X then - return false - elseif X then - return false - else - return false - end - -end -]] - -TEST [[ -while true do -end - - -]] - -TEST [[ -while true do -end - - -]] - -TEST [[ -while X do - X = 1 -end - -print(1) -]] - -TEST [[ ----@diagnostic disable: undefined-global - -while true do - if not X then - break - end -end - -print(1) - -do return end -]] - -TEST [[ ----@type unknown -local t - -local _ = t -]] - -TEST [[ ----@diagnostic disable: duplicate-set-field ----@class A -local m = {} - -function m.ff() end - -function m.ff(x) end - -m.ff(1) -]] - -TEST [[ -local done = false - -local function set_done() - done = true -end - -while not done do - set_done() -end - -print(1) -]] diff --git a/test/diagnostics/count-down-loop.lua b/test/diagnostics/count-down-loop.lua new file mode 100644 index 000000000..f4e385f5d --- /dev/null +++ b/test/diagnostics/count-down-loop.lua @@ -0,0 +1,29 @@ +TEST [[ +for i = do + print(i) +end +]] + +TEST [[ +for i = do + print(i) +end +]] + +TEST [[ +for i = do + print(i) +end +]] + +TEST [[ +for i = do + print(i) +end +]] + +TEST [[ +for i = 1, 1 do + print(i) +end +]] diff --git a/test/diagnostics/deprecated.lua b/test/diagnostics/deprecated.lua new file mode 100644 index 000000000..c5486752f --- /dev/null +++ b/test/diagnostics/deprecated.lua @@ -0,0 +1,21 @@ +TEST [[ +local _ = +]] + +TEST [[ +T = {} +---@deprecated # comment +T.x = 1 + +print() +]] + +TEST [[ +T = {} + +---@deprecated +function T:ff() +end + +() +]] diff --git a/test/diagnostics/discard-returns.lua b/test/diagnostics/discard-returns.lua new file mode 100644 index 000000000..2e3483908 --- /dev/null +++ b/test/diagnostics/discard-returns.lua @@ -0,0 +1,17 @@ +TEST [[ +---@nodiscard +local function f() + return 1 +end + + +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +X = f() +]] diff --git a/test/diagnostics/doc-field-no-class.lua b/test/diagnostics/doc-field-no-class.lua new file mode 100644 index 000000000..87db518cc --- /dev/null +++ b/test/diagnostics/doc-field-no-class.lua @@ -0,0 +1,16 @@ +TEST [[ +---@field +---@class Class +]] + +TEST [[ +---@class Class + +---@field +]] + +TEST [[ +---@class Class +--- +---@field x Class +]] diff --git a/test/diagnostics/duplicate-doc-alias.lua b/test/diagnostics/duplicate-doc-alias.lua new file mode 100644 index 000000000..0373fee98 --- /dev/null +++ b/test/diagnostics/duplicate-doc-alias.lua @@ -0,0 +1,10 @@ +TEST [[ +---@alias integer +---@alias integer +]] + +TEST [[ +---@class A +---@class B +---@alias B +]] diff --git a/test/diagnostics/duplicate-doc-field.lua b/test/diagnostics/duplicate-doc-field.lua new file mode 100644 index 000000000..8f3853359 --- /dev/null +++ b/test/diagnostics/duplicate-doc-field.lua @@ -0,0 +1,38 @@ +TEST [[ +---@class Class +---@field Class +---@field Class +]] + +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 fun(eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(eventName: '"won"', cb: fun(s: string)) +--- @field fun(eventName: '"died"', cb: fun(i: integer)) +local emit = {} +]] + +TEST [[ +---@class A + +---@class B +---@field [integer] A +---@field [A] true +]] + +TEST [[ +---@class A + +---@class B +---@field [] A +---@field [] true +]] diff --git a/test/diagnostics/duplicate-doc-param.lua b/test/diagnostics/duplicate-doc-param.lua new file mode 100644 index 000000000..42eb73d3d --- /dev/null +++ b/test/diagnostics/duplicate-doc-param.lua @@ -0,0 +1,12 @@ +TEST [[ +---@class Class +---@param Class +---@param y Class +---@param Class +local function f(x, y) + return x, y +end + +local _ +f(_, _) +]] diff --git a/test/diagnostics/duplicate-index.lua b/test/diagnostics/duplicate-index.lua new file mode 100644 index 000000000..3289c736b --- /dev/null +++ b/test/diagnostics/duplicate-index.lua @@ -0,0 +1,24 @@ +TEST [[ +return { + = 1, + y = 2, + = 3, +} +]] + +TEST [[ +return { + x = 1, + y = 2, +}, { + x = 1, + y = 2, +} +]] + +TEST [[ +return { + 1, , 3, + [] = 4, +} +]] diff --git a/test/diagnostics/duplicate-set-field.lua b/test/diagnostics/duplicate-set-field.lua new file mode 100644 index 000000000..469bc3eaa --- /dev/null +++ b/test/diagnostics/duplicate-set-field.lua @@ -0,0 +1,74 @@ +TEST [[ +local m = {} + +function () +end + +function () +end + +return m +]] + +TEST [[ +local m = {} + +function () +end + +do + function () + 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 +X = {} + +function () end + +function () end +]] + +TEST [[ +---@meta + +---@class A +X = {} + +function X.f() end + +function X.f() end +]] + +TEST [[ +---@class A +X = {} + +if true then + function X.f() end +else + function X.f() end +end +]] diff --git a/test/diagnostics/empty-block.lua b/test/diagnostics/empty-block.lua new file mode 100644 index 000000000..750397a4b --- /dev/null +++ b/test/diagnostics/empty-block.lua @@ -0,0 +1,32 @@ +TEST [[ + +]] + +TEST [[ + +]] + +TEST [[ +if true then +else + return +end +]] + +TEST [[ +while true do +end +]] + +TEST [[ + +]] + +TEST [[ + +]] diff --git a/test/diagnostics/global-element.lua b/test/diagnostics/global-element.lua new file mode 100644 index 000000000..0c31badee --- /dev/null +++ b/test/diagnostics/global-element.lua @@ -0,0 +1,63 @@ +local config = require 'config' + +TEST [[ +local x = 123 +x = 321 + = "global" + = "global" +]] + +TEST [[ +local function test1() + print() +end + +function () + print() +end +]] + +TEST [[ +local function closure1() + local elem1 = 1 + = 2 +end + +function () + local elem1 = 1 + = 2 +end +]] + +-- add elements to exemption list +config.set(nil, 'Lua.diagnostics.globals', +{ + 'GLOBAL1', + 'GLOBAL2', + 'GLOBAL_CLOSURE' +}) + +TEST [[ +GLOBAL1 = "allowed" + = "not allowed" + = "not allowed" +]] + +TEST [[ +function GLOBAL1() + print() +end +]] + +TEST [[ +local function closure1() + local elem1 = 1 + GLOBAL1 = 2 +end + +function GLOBAL_CLOSURE() + local elem1 = 1 + GLOBAL2 = 2 + = 2 +end +]] diff --git a/test/diagnostics/global-in-nil-env.lua b/test/diagnostics/global-in-nil-env.lua new file mode 100644 index 000000000..a0b8cd3e6 --- /dev/null +++ b/test/diagnostics/global-in-nil-env.lua @@ -0,0 +1,44 @@ +TEST [[ +local _ +print(_) +local _ +print(_) +local _ENV +(_ENV) -- ็”ฑไบŽ้‡ๅฎšไน‰ไบ†_ENV๏ผŒๅ› ๆญคprintๅ˜ไธบไบ†ๆœชๅฎšไน‰ๅ…จๅฑ€ๅ˜้‡ +]] + +TEST [[ +_ENV = nil +() -- `print` and `A` should warning +]] + +TEST [[ +local _ENV = nil +() -- `print` and `A` should warning +]] + +TEST [[ +_ENV = {} +print(A) -- no warning +]] + +TEST [[ +local _ENV = {} +print(A) -- no warning +]] + +TEST [[ +_ENV = nil + = 1 --> _ENV.GLOBAL = 1 +]] + +TEST [[ +_ENV = nil +local _ = --> local _ = _ENV.print +]] + +TEST [[ +local function foo(_ENV) + Joe = "human" +end +]] diff --git a/test/diagnostics/incomplete-signature-doc.lua b/test/diagnostics/incomplete-signature-doc.lua new file mode 100644 index 000000000..7cd144c03 --- /dev/null +++ b/test/diagnostics/incomplete-signature-doc.lua @@ -0,0 +1,398 @@ +-- ------------------------------------- +-- about the structure of these test cases +-- +-- the following test cases are grouped by the number of parameters and return values of the functions +-- so first global functions with: +-- no parameter and return value (FG), one parameter (FGP), two parameters (FGPP), +-- one return value (FGR), two return values (FGRR) and parameter and return value (FGPR) +-- after that, these groups are also done for local functions (FL, FLP, ...) +-- +-- in these groups, different versions of documentation are tested: +-- no comment, simple comment, @async annotation (which is no signature doc), +-- incomplete signature doc (only part of the necessary @param or @return annotations, if possible) - the only cases that should generating warnings +-- and complete signature docs (all necessary @param and @return annotations) +-- ------------------------------------- + +-- global functions no parameter, no return value +-- no incomplete signature docs possible + +TEST [[ +function FG0() +end + +---comment +function FG1() +end + +---@async +function FG1_() +end +]] + +-- global functions with single parameter, no return value +-- no incomplete signature docs possible +TEST [[ +function FGP0(p) + print(p) +end + +---comment +function FGP1(p) + print(p) +end + +---@async +function FGP1_(p) + print(p) +end + +---comment +---@param p any +function FGP2(p) + print(p) +end +]] + +-- global functions with two parameters, no return value +-- incomplete signature docs when exactly one of the parameters is documented +TEST [[ +function FGPP0(p0, p1) + print(p0, p1) +end + +---comment +function FGPP1(p0, p1) + print(p0, p1) +end + +---@async +function FGPP1_(p0, p1) + print(p0, p1) +end + +---comment +---@param p0 any +function FGPP2(p0, ) + print(p0, p1) +end + +---comment +---@param p1 any +function FGPP2_(, p1) + print(p0, p1) +end + +---comment +---@param p0 any +---@param p1 any +function FGPP3(p0, p1) + print(p0, p1) +end +]] + +-- global functions with no parameter, single return value +-- no incomplete signature docs possible +TEST [[ +function FGR0() + return 0 +end + +---comment +function FGR1() + return 0 +end + +---@async +function FGR1_() + return 0 +end + +---comment +---@return integer +function FGR2() + return 0 +end +]] + +-- global functions with no parameter, two return values +-- incomplete signature docs when exactly one of the return values is documented +TEST [[ +function FGRR0() + return 0, 1 +end + +---comment +function FGRR1() + return 0, 1 +end + +---@async +function FGRR1_() + return 0, 1 +end + +---comment +---@return integer +function FGRR2() + return 0, +end + +---comment +---@return integer +---@return integer +function FGRR3() + return 0, 1 +end +]] + +-- global functions with one parameter, one return value +-- incomplete signature docs when exactly one of parameter or return value is documented +TEST [[ +function FGPR0(p) + print(p) + return 0 +end + +---comment +function FGPR1(p) + print(p) + return 0 +end + +---@async +function FGPR1_(p) + print(p) + return 0 +end + +---comment +---@param p any +function FGPR2(p) + print(p) + return +end + +---comment +---@return integer +function FGPR3() + print(p) + return 0 +end + +---comment +---@param p any +---@return integer +function FGPR4(p) + print(p) + return 0 +end +]] + +-- local functions with no parameter, no return value +-- no incomplete signature docs possible +TEST [[ +local function FL0() +end + +FL0() + +---comment +local function FL1() +end + +FL1() + +---@async +local function FL1_() +]] + +-- local functions with single parameter, no return value +-- no incomplete signature docs possible +TEST [[ +local function FLP0(p) + print(p) +end + +FLP0(0) + +---comment +local function FLP1(p) + print(p) +end + +FLP1(0) + +---@async +local function FLP1_(p) + print(p) +end + +---comment +---@param p any +local function FLP2(p) + print(p) +end + +FLP2(0) +]] + +-- local functions with two parameters, no return value +-- incomplete signature docs when exactly one of the parameters is documented +TEST [[ +local function FLPP0(p0, p1) + print(p0, p1) +end + +FLPP0(0, 1) + +---comment +local function FLPP1(p0, p1) + print(p0, p1) +end + +FLPP1(0, 1) + +---@async +local function FLPP1_(p0, p1) + print(p0, p1) +end + +---comment +---@param p0 any +local function FLPP2(p0, ) + print(p0, p1) +end + +FLPP2(0, 1) + +---comment +---@param p0 any +---@param p1 any +local function FLPP3(p0, p1) + print(p0, p1) +end + +FLPP3(0, 1) +]] + +-- local functions with no parameter, single return value +-- no incomplete signature docs possible +TEST [[ +local function FLR0() + return 0 +end + +local vr0 = FLR0() + +---comment +local function FLR1() + return 0 +end + +---@async +local function FLR1_() + return 0 +end + +local vr1 = FLR1() + +---comment +---@return integer +local function FLR2() + return 0 +end + +local vr2 = FLR2() +]] + +-- local functions with no parameter, two return values +-- incomplete signature docs when exactly one of the return values is documented +TEST [[ +local function FLRR0() + return 0, 1 +end + +local vrr0, _ = FLRR0() + +---comment +local function FLRR1() + return 0, 1 +end + +local vrr1, _ = FLRR1() + +---@async +local function FLRR1_() + return 0, 1 +end + +---comment +---@return integer +local function FLRR2() + return 0, +end + +local vrr2, _ = FLRR2() + +---comment +---@return integer +---@return integer +local function FLRR3() + return 0, 1 +end + +local vrr3, _ = FLRR3() +]] + +-- local functions with one parameter, one return value +-- incomplete signature docs when exactly one of parameter or return value is documented +TEST [[ +local function FLPR0(p) + print(p) + return 0 +end + +local vpr0 = FLPR0(0) + +---comment +local function FLPR1(p) + print(p) + return 0 +end + +---@async +local function FLPR1_(p) + print(p) + return 0 +end + +local vpr1 = FLPR1(0) + +---comment +---@param p any +local function FLPR2(p) + print(p) + return +end + +local vpr2 = FLPR2(0) + +---comment +---@return integer +local function FLPR3() + print(p) + return 0 +end + +local vpr3 = FLPR3(0) + +---comment +---@param p any +---@return integer +local function FLPR4(p) + print(p) + return 0 +end + +local vpr4 = FLPR4(0) +]] diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index 2c5dba116..99a5dc248 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -4,9 +4,15 @@ local config = require 'config' local util = require 'utility' local catch = require 'catch' -config.get(nil, 'Lua.diagnostics.neededFileStatus')['deprecated'] = 'Any' -config.get(nil, 'Lua.diagnostics.neededFileStatus')['type-check'] = 'Any' -config.get(nil, 'Lua.diagnostics.neededFileStatus')['await-in-sync'] = 'Any' +local status = config.get(nil, 'Lua.diagnostics.neededFileStatus') + +for key in pairs(status) do + status[key] = 'Any!' +end + +config.set('nil', 'Lua.type.castNumberToInteger', false) +config.set('nil', 'Lua.type.weakUnionCheck', false) +config.set('nil', 'Lua.type.weakNilCheck', false) rawset(_G, 'TEST', true) @@ -27,14 +33,19 @@ local function founded(targets, results) end ---@diagnostic disable: await-in-sync -function TEST(script, ...) +function TEST(script) local newScript, catched = catch(script, '!') - files.setText('', newScript) - files.open('') + files.setText(TESTURI, newScript) + files.open(TESTURI) local origins = {} + local filteds = {} local results = {} - core('', false, function (result) - results[#results+1] = { result.start, result.finish } + core(TESTURI, false, function (result) + if DIAG_CARE == result.code + or DIAG_CARE == '*' then + results[#results+1] = { result.start, result.finish } + filteds[#filteds+1] = result + end origins[#origins+1] = result end) @@ -46,8 +57,72 @@ function TEST(script, ...) assert(#catched['!'] == 0) end - files.remove('') + files.remove(TESTURI) + + return function (callback) + callback(filteds) + end +end + +local function check(name) + DIAG_CARE = name + require('diagnostics.' .. name) end -require 'diagnostics.common' -require 'diagnostics.type-check' +check 'ambiguity-1' +check 'assign-type-mismatch' +check 'await-in-sync' +check 'cast-local-type' +check 'cast-type-mismatch' +check 'circle-doc-class' +check 'close-non-object' +check 'code-after-break' +check 'count-down-loop' +check 'deprecated' +check 'discard-returns' +check 'doc-field-no-class' +check 'duplicate-doc-alias' +check 'duplicate-doc-field' +check 'duplicate-doc-param' +check 'duplicate-index' +check 'duplicate-set-field' +check 'empty-block' +check 'global-element' +check 'global-in-nil-env' +check 'incomplete-signature-doc' +check 'inject-field' +check 'invisible' +check 'lowercase-global' +check 'missing-fields' +check 'missing-global-doc' +check 'missing-local-export-doc' +check 'missing-parameter' +check 'missing-return-value' +check 'missing-return' +check 'need-check-nil' +check 'newfield-call' +check 'newline-call' +check 'not-yieldable' +check 'param-type-mismatch' +check 'redefined-local' +check 'redundant-parameter' +check 'redundant-return-value' +check 'redundant-return' +check 'redundant-value' +check 'return-type-mismatch' +check 'trailing-space' +check 'unbalanced-assignments' +check 'undefined-doc-class' +check 'undefined-doc-name' +check 'undefined-doc-param' +check 'undefined-env-child' +check 'undefined-field' +check 'undefined-global' +check 'unknown-cast-variable' +check 'unknown-diag-code' +check 'unknown-operator' +check 'unreachable-code' +check 'unused-function' +check 'unused-label' +check 'unused-local' +check 'unused-vararg' diff --git a/test/diagnostics/inject-field.lua b/test/diagnostics/inject-field.lua new file mode 100644 index 000000000..9bb0f8fcf --- /dev/null +++ b/test/diagnostics/inject-field.lua @@ -0,0 +1,84 @@ +TEST [[ +---@class Class +local m = {} + +m.xx = 1 -- OK + +---@type Class +local m + +m.xx = 1 -- OK +m. = 1 -- Warning +]] + +TEST [[ +---@class Class +local m = {} + +m.xx = 1 -- OK + +---@class Class +local m + +m.xx = 1 -- OK +m.yy = 1 -- OK +]] + +TEST [[ +---@type { xx: number } +local m + +m.xx = 1 -- OK +m. = 1 -- Warning +]] + +TEST [[ +---@type { xx: number, [any]: any } +local m + +m.xx = 1 -- OK +m.yy = 1 -- OK +]] + +TEST [[ +---@class Class +---@field x number + +---@type Class +local t + +t.x = 1 -- OK +t. = 2 -- Warning +]] + +TEST [[ +---@class Class +---@field x number +---@field [any] any + +---@type Class +local t + +t.x = 1 -- OK +t.y = 2 -- OK +]] + + +TEST [[ +---@class (exact) Class +---@field x number +local m = { + x = 1, -- OK + = 2, -- Warning +} + +m.x = 1 -- OK +m. = 2 -- Warning + +function m:init() -- OK + self.x = 1 -- OK + self. = 2 -- Warning + function self:() -- Warning + end +end +]] diff --git a/test/diagnostics/invisible.lua b/test/diagnostics/invisible.lua new file mode 100644 index 000000000..2fc6791e8 --- /dev/null +++ b/test/diagnostics/invisible.lua @@ -0,0 +1,145 @@ +local config = require 'config' + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field public z number +local t +print(t.x) +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field public z number + +---@type A +local t + +print(t.) +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field public z number + +---@class B: A +local t + +print(t.y) +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field public z number + +---@class B: A + +---@type B +local t + +print(t.) +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field public z number + +---@class B: A + +---@type B +local t + +print(t.z) +]] +TEST [[ +---@class A +---@field _id number + +---@type A +local t + +print(t._id) +]] + +config.set(nil, 'Lua.doc.privateName', { '_*' }) +TEST [[ +---@class A +---@field _id number + +---@type A +local t + +print(t.) + +---@class B: A +local t2 + +print(t2.) +]] +config.set(nil, 'Lua.doc.privateName', nil) + +config.set(nil, 'Lua.doc.protectedName', { '_*' }) +TEST [[ +---@class A +---@field _id number + +---@type A +local t + +print(t.) + +---@class B: A +local t2 + +print(t2._id) +]] +config.set(nil, 'Lua.doc.protectedName', nil) + +TEST [[ +---@class A +---@field private x number +local mt = {} + +function mt:init() + print(self.x) +end +]] + +TEST [[ +---@diagnostic disable: unused-local +---@diagnostic disable: missing-fields +---@class A +---@field private x number +local mt = {} + +function mt:init() + ---@type A + local obj = {} + + obj.x = 1 +end +]] + +TEST [[ +---@diagnostic disable: unused-local +---@diagnostic disable: missing-fields +---@class A +---@field private x number +local mt = {} + +mt.init = function () + ---@type A + local obj = {} + + obj.x = 1 +end +]] diff --git a/test/diagnostics/lowercase-global.lua b/test/diagnostics/lowercase-global.lua new file mode 100644 index 000000000..bf73b8c8f --- /dev/null +++ b/test/diagnostics/lowercase-global.lua @@ -0,0 +1,39 @@ +TEST [[ + = 1 +tostring = 1 +ROOT = 1 +_G.bb = 1 +]] + +TEST [[ +---@diagnostic disable-next-line +x = 1 +]] + +TEST [[ +---@diagnostic disable-next-line: lowercase-global +x = 1 +]] + +TEST [[ +---@diagnostic disable-next-line: unused-local + = 1 +]] + +TEST [[ +---@diagnostic disable +x = 1 +]] + +TEST [[ +---@diagnostic disable +---@diagnostic enable + = 1 +]] + +TEST [[ +---@diagnostic disable +---@diagnostic disable +---@diagnostic enable +x = 1 +]] diff --git a/test/diagnostics/missing-fields.lua b/test/diagnostics/missing-fields.lua new file mode 100644 index 000000000..ab87f81d8 --- /dev/null +++ b/test/diagnostics/missing-fields.lua @@ -0,0 +1,233 @@ +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = { + x = 1, + y = 2, + z = 3, +} +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@type A +local t = { + x = 1, + z = 3, +} +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@param a A +local function f(a) end + +f +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@param a A +local function f(a) end + +f +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@param a A +local function f(a) end + +f +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@param a A +local function f(a) end + +f { + x = 1, + y = 2, + z = 3, +} +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +---@field y? number +---@field z number + +---@param a A +local function f(a) end + +f { + x = 1, + z = 3, +} +]] + +TEST [[ +---@diagnostic disable: unused-local +---@class A +---@field x number +local t = {} +]] + +TEST [[ +---@diagnostic disable: unused-local + +---@class A +---@field x number + +---@class A +local t = {} +]] + +TEST [[ +---@diagnostic disable: unused-local + +---@class Foo +---@field a number +---@field b number +---@field c number + +---@type Foo|Foo[] +local a = { + { + a = 1, + b = 2, + c = 3, + } +} +]] + +TEST [[ +---@diagnostic disable: unused-local + +---@class Foo +---@field a number +---@field b number +---@field c number + +---@class Bar +---@field ba number +---@field bb number +---@field bc number + +---@type Foo|Bar +local b = { + a = 1, + b = 2, + c = 3, +} +]] + +TEST [[ +---@class A +---@field x integer + +---@type A +return +]] + +TEST [[ +---@class A +---@field x number + +---@class B +---@field y number + +---@type A|B +local t = +]] + +TEST [[ +---@class A +---@field x number + +---@class B +---@field y number + +---@type A|B +local t = { + y = 1, +} +]] diff --git a/test/diagnostics/missing-global-doc.lua b/test/diagnostics/missing-global-doc.lua new file mode 100644 index 000000000..de5250fd9 --- /dev/null +++ b/test/diagnostics/missing-global-doc.lua @@ -0,0 +1,297 @@ +-- check global functions +TEST [[ + + +---comment +function FG1() +end +]] + +TEST [[ +function FGP0() + print(p) +end + +---comment +function FGP1() + print(p) +end + +---comment +---@param p any +function FGP2(p) + print(p) +end +]] + +TEST [[ +function FGPP0(, ) + print(p0, p1) +end + +---comment +function FGPP1(, ) + print(p0, p1) +end + +---comment +---@param p0 any +function FGPP2(p0, ) + print(p0, p1) +end + +---comment +---@param p0 any +---@param p1 any +function FGPP3(p0, p1) + print(p0, p1) +end +]] + +TEST [[ +function FGR0() + return +end + +---comment +function FGR1() + return +end + +---comment +---@return integer +function FGR2() + return 0 +end +]] + +TEST [[ +function FGRR0() + return , +end + +---comment +function FGRR1() + return , +end + +---comment +---@return integer +function FGRR2() + return 0, +end + +---comment +---@return integer +---@return integer +function FGRR3() + return 0, 1 +end +]] + + +TEST [[ +function FGPR0() + print(p) + return +end + +---comment +function FGPR1() + print(p) + return +end + +---comment +---@param p any +function FGPR2(p) + print(p) + return +end + +---comment +---@return integer +function FGPR3() + print(p) + return 0 +end + +---comment +---@param p any +---@return integer +function FGPR4(p) + print(p) + return 0 +end +]] + +-- check local functions + +TEST [[ +local function FL0() +end + +FL0() + +---comment +local function FL1() +end + +FL1() +]] + +TEST [[ +local function FLP0(p) + print(p) +end + +FLP0(0) + +---comment +local function FLP1(p) + print(p) +end + +FLP1(0) + +---comment +---@param p any +local function FLP2(p) + print(p) +end + +FLP2(0) +]] + +TEST [[ +local function FLPP0(p0, p1) + print(p0, p1) +end + +FLPP0(0, 1) + +---comment +local function FLPP1(p0, p1) + print(p0, p1) +end + +FLPP1(0, 1) + +---comment +---@param p0 any +local function FLPP2(p0, p1) + print(p0, p1) +end + +FLPP2(0, 1) + +---comment +---@param p0 any +---@param p1 any +local function FLPP3(p0, p1) + print(p0, p1) +end + +FLPP3(0, 1) +]] + +TEST [[ +local function FLR0() + return 0 +end + +local vr0 = FLR0() + +---comment +local function FLR1() + return 0 +end + +local vr1 = FLR1() + +---comment +---@return integer +local function FLR2() + return 0 +end + +local vr2 = FLR2() +]] + +TEST [[ +local function FLRR0() + return 0, 1 +end + +local vrr0, _ = FLRR0() + +---comment +local function FLRR1() + return 0, 1 +end + +local vrr1, _ = FLRR1() + +---comment +---@return integer +local function FLRR2() + return 0, 1 +end + +local vrr2, _ = FLRR2() + +---comment +---@return integer +---@return integer +local function FLRR3() + return 0, 1 +end + +local vrr3, _ = FLRR3() +]] + +TEST [[ +local function FLPR0(p) + print(p) + return 0 +end + +local vpr0 = FLPR0(0) + +---comment +local function FLPR1(p) + print(p) + return 0 +end + +local vpr1 = FLPR1(0) + +---comment +---@param p any +local function FLPR2(p) + print(p) + return 0 +end + +local vpr2 = FLPR2(0) + +---comment +---@return integer +local function FLPR3(p) + print(p) + return 0 +end + +local vpr3 = FLPR3(0) + +---comment +---@param p any +---@return integer +local function FLPR4(p) + print(p) + return 0 +end + +local vpr4 = FLPR4(0) +]] diff --git a/test/diagnostics/missing-local-export-doc.lua b/test/diagnostics/missing-local-export-doc.lua new file mode 100644 index 000000000..3d9422161 --- /dev/null +++ b/test/diagnostics/missing-local-export-doc.lua @@ -0,0 +1,175 @@ +-- check global functions +TEST [[ +local mod = {} + +local + +---comment +local function fl1() +end + +local function fl2() +end + +function FG0() +end + +mod.fl0 = fl0 +mod.fl1 = fl1 +return mod +]] + +TEST [[ +local mod = {} + +local function flp0() + print(p) +end + +---comment +local function flp1() + print(p) +end + +---comment +---@param p any +local function flp2(p) + print(p) +end + +mod.flp0 = flp0 +mod.flp1 = flp1 +return mod +]] + +TEST [[ +local mod = {} + +local function flpp0(, ) + print(p0, p1) +end + +---comment +local function flpp1(, ) + print(p0, p1) +end + +---comment +---@param p0 any +local function flpp2(p0, ) + print(p0, p1) +end + +---comment +---@param p0 any +---@param p1 any +local function flpp3(p0, p1) + print(p0, p1) +end + +mod.flpp0 = flpp0 +mod.flpp1 = flpp1 +mod.flpp2 = flpp2 +mod.flpp3 = flpp3 +return mod +]] + +TEST [[ +local mod = {} + +local function flr0() + return +end + +---comment +local function flr1() + return +end + +---comment +---@return integer +local function flr2() + return 0 +end + +mod.flr0 = flr0 +mod.flr1 = flr1 +mod.flr2 = flr2 +return mod +]] + +TEST [[ +local mod = {} + +local function flrr0() + return , +end + +---comment +local function flrr1() + return , +end + +---comment +---@return integer +local function flrr2() + return 0, +end + +---comment +---@return integer +---@return integer +local function flrr3() + return 0, 1 +end + +mod.flrr0 = flrr0 +mod.flrr1 = flrr1 +mod.flrr2 = flrr2 +return mod +]] + +TEST [[ +local mod = {} + +local function flpr0() + print(p) + return +end + +---comment +local function flpr1() + print(p) + return +end + +---comment +---@param p any +local function flpr2(p) + print(p) + return +end + +---comment +---@return integer +local function flpr3() + print(p) + return 0 +end + +---comment +---@param p any +---@return integer +local function flpr4(p) + print(p) + return 0 +end + +mod.flpr0 = flpr0 +mod.flpr1 = flpr1 +mod.flpr2 = flpr2 +mod.flpr3 = flpr3 +mod.flpr4 = flpr4 +return mod +]] diff --git a/test/diagnostics/missing-parameter.lua b/test/diagnostics/missing-parameter.lua new file mode 100644 index 000000000..154d630b8 --- /dev/null +++ b/test/diagnostics/missing-parameter.lua @@ -0,0 +1,90 @@ + +TEST [[ +local function x(a, b) + return a, b +end +x(1) +]] + +TEST [[ +---@param a integer +---@param b integer +local function x(a, b) + return a, b +end + +]] + +TEST [[ +---@param a integer +---@param b integer +local function x(a, b) + return a, b +end + +]] + +TEST [[ +---@param a integer +---@param b integer +---@param ... integer +local function x(a, b, ...) + return a, b, ... +end +x(1, 2) +]] + +TEST [[ +---@param a integer +---@param b integer +local function f(a, b) +end + +f(...) +]] + +TEST [[ +---@param a integer +---@param b integer +local function f(a, b) +end + +local function return2Numbers() + return 1, 2 +end + +f(return2Numbers()) +]] + +TEST [[ +---@param a integer +---@param b? integer +local function x(a, b) + return a, b +end +x(1) +]] + +TEST [[ +---@param b integer? +local function x(a, b) + return a, b +end +x(1) +]] + +TEST [[ +---@param b integer|nil +local function x(a, b) + return a, b +end +x(1) +]] + +TEST [[ +local t = {} + +function t:init() end + + +]] diff --git a/test/diagnostics/missing-return-value.lua b/test/diagnostics/missing-return-value.lua new file mode 100644 index 000000000..3bad79748 --- /dev/null +++ b/test/diagnostics/missing-return-value.lua @@ -0,0 +1,34 @@ +TEST [[ +---@type fun():number +local function f() + +end +]] + +TEST [[ +---@return number +function F() + +end +]] + +TEST [[ +---@return number, number +function F() + 1 +end +]] + +TEST [[ +---@return number, number? +function F() + return 1 +end +]] + +TEST [[ +---@return ... +function F() + return +end +]] diff --git a/test/diagnostics/missing-return.lua b/test/diagnostics/missing-return.lua new file mode 100644 index 000000000..b8c1e7d37 --- /dev/null +++ b/test/diagnostics/missing-return.lua @@ -0,0 +1,158 @@ +TEST [[ +---@type fun():number +local function f() +end +]] + +TEST [[ +---@type fun():number? +local function f() +end +]] + +TEST [[ +---@type fun():... +local function f() +end +]] + +TEST [[ +---@return number +function F() + X = 1 +end +]] +TEST [[ +local A +---@return number +function F() + if A then + return 1 + end +end +]] + +TEST [[ +local A, B +---@return number +function F() + if A then + return 1 + elseif B then + return 2 + end +end +]] + +TEST [[ +local A, B +---@return number +function F() + if A then + return 1 + elseif B then + return 2 + else + return 3 + end +end +]] + +TEST [[ +local A, B +---@return number +function F() + if A then + elseif B then + return 2 + else + return 3 + end +end +]] + +TEST [[ +---@return any +function F() + X = 1 +end +]] + +TEST [[ +---@return any, number +function F() + X = 1 +end +]] + +TEST [[ +---@return number, any +function F() + X = 1 +end +]] + +TEST [[ +---@return any, any +function F() + X = 1 +end +]] + +TEST [[ +local A +---@return number +function F() + for _ = 1, 10 do + if A then + return 1 + end + end + error('should not be here') +end +]] + +TEST [[ +local A +---@return number +function F() + while true do + if A then + return 1 + end + end +end +]] + +TEST [[ +local A +---@return number +function F() + while A do + if A then + return 1 + end + end +end +]] + +TEST [[ +local A +---@return number +function F() + while A do + if A then + return 1 + else + return 2 + end + end +end +]] + +TEST [[ +---@return number? +function F() + +end +]] diff --git a/test/diagnostics/need-check-nil.lua b/test/diagnostics/need-check-nil.lua new file mode 100644 index 000000000..c4e3bba65 --- /dev/null +++ b/test/diagnostics/need-check-nil.lua @@ -0,0 +1,68 @@ +TEST [[ +---@type string? +local x + +local s = :upper() +]] + +TEST [[ +---@type string? +local x + +S = :upper() +]] + +TEST [[ +---@type string? +local x + +if x then + S = x:upper() +end +]] + +TEST [[ +---@type string? +local x + +if not x then + x = '' +end + +S = x:upper() +]] + +TEST [[ +---@type fun()? +local x + +S = () +]] + +TEST [[ +---@type integer? +local x + +T = {} +T[] = 1 +]] + +TEST [[ +local x, y +local z = x and y + +print(z.y) +]] + +TEST [[ +local x, y +function x() + y() +end + +function y() + x() +end + +x() +]] diff --git a/test/diagnostics/newfield-call.lua b/test/diagnostics/newfield-call.lua new file mode 100644 index 000000000..63de9db9f --- /dev/null +++ b/test/diagnostics/newfield-call.lua @@ -0,0 +1,15 @@ +TEST [[ +return { + +} +]] + +TEST [[ +return { + +} +]] diff --git a/test/diagnostics/newline-call.lua b/test/diagnostics/newline-call.lua new file mode 100644 index 000000000..ca160aa3d --- /dev/null +++ b/test/diagnostics/newline-call.lua @@ -0,0 +1,34 @@ +TEST [[ +:sub(1, 1) +]] + +TEST [[ +print() +('string') +]] + +TEST [[ +print +{} +{} +]] + +TEST [[ +local x +return x + : f(1) + : f(1) +]] + +TEST [[ +print() +'string' +]] + +TEST [[ +print +{ + x = 1, +} +]] diff --git a/test/diagnostics/not-yieldable.lua b/test/diagnostics/not-yieldable.lua new file mode 100644 index 000000000..81e972eea --- /dev/null +++ b/test/diagnostics/not-yieldable.lua @@ -0,0 +1,48 @@ +TEST [[ +---@param cb fun() +local function f(cb) + return cb +end + +---@async +local function af() + return nil +end + +f() +]] + +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 + +---@async +local function af() + f(function () ---@async + return nil + end) +end + +return af +]] + +TEST [[ +local _ = type(function () ---@async + return nil +end) +]] diff --git a/test/diagnostics/param-type-mismatch.lua b/test/diagnostics/param-type-mismatch.lua new file mode 100644 index 000000000..e31e99339 --- /dev/null +++ b/test/diagnostics/param-type-mismatch.lua @@ -0,0 +1,248 @@ +TEST [[ +---@param x number +local function f(x) end + +f() +]] + +TEST [[ +---@class A + +---@param n A +local function f(n) +end + +---@class B +local a = {} + +---@type A? +a.x = XX + +f(a.x) +]] +TEST [[ +---@alias A string|boolean + +---@param x string|boolean +local function f(x) end + +---@type A +local x + +f(x) +]] + +TEST [[ +---@alias A string|boolean + +---@param x A +local function f(x) end + +---@type string|boolean +local x + +f(x) +]] + +TEST [[ +---@param b boolean +local function f(b) +end + +---@type boolean +local t + +if t then + f(t) +end +]] + +TEST [[ +---@enum A +local t = { + x = 1, + y = 2, +} + +---@param x A +local function f(x) +end + +f() +f(t.x) +f(1) +f() +]] + +TEST [[ +---@enum A +local t = { + x = { h = 1 }, + y = { h = 2 }, +} + +---@param x A +local function f(x) +end + +f(t.x) +f(t.y) +f() +]] + +TEST [[ +---@enum(key) A +local t = { + x = 1, + ['y'] = 2, +} + +---@param x A +local function f(x) +end + +f('x') +f('y') +f() +]] + +TEST [[ +---@generic T: string | boolean | table +---@param x T +---@return T +local function f(x) + return x +end + +f() +]] + +TEST [[ +---@param opts {a:number, b:number} +local function foo(opts) + +end + +---@param opts {a:number, b:number} +local function bar(opts) + foo(opts) +end +]] + +TEST [[ +---@param opts {a:number, b:number} +local function foo(opts) + +end + +---@param opts {c:number, d:number} +local function bar(opts) + foo() -- this should raise linting error +end +]] + +TEST [[ +---@param opts {[number]: boolean} +local function foo(opts) + +end + +---@param opts {[1]: boolean} +local function bar(opts) + foo(opts) +end +]] + +TEST [[ +---@generic T +---@param v1 T +---@param v2 T|table +local function func(v1, v2) +end + +func('hello', 'world') +]] + +TEST [[ +---@generic T1, T2, T3, T4, T5 +---@param f fun(): T1?, T2?, T3?, T4?, T5? +---@return T1?, T2?, T3?, T4?, T5? +local function foo(f) + return f() +end + +local a, b = foo(function() + return 1 +end) +]] + +TEST [[ +---@generic T1, T2, T3, T4, T5 +---@param f fun(): T1|nil, T2|nil, T3|nil, T4|nil, T5|nil +---@return T1?, T2?, T3?, T4?, T5? +local function foo(f) + return f() +end + +local a, b = foo(function() + return 1 +end) +]] + +TEST [[ +---@param v integer +---@return boolean +local function is_string(v) + return type(v) == 'string' +end + +print(is_string(3)) +]] + +TEST [[ +---@param p integer|string +local function get_val(p) + local is_number = type(p) == 'number' + return is_number and p or p +end + +get_val('hi') +]] + +TEST [[ +---@class Class +local Class = {} + +---@param source string +function Class.staticCreator(source) + +end + +Class.staticCreator() +ClassstaticCreator() -- Expecting a waring +]] + +TEST [[ +---@class A + +---@class B : A + +---@class C : B + +---@class D : B + +---@param x A +local function func(x) end + +---@type C|D +local var +func(var) +]] + +TEST [[ +---@class MyClass +---@overload fun(x : string) : MyClass +local MyClass = {} + +local w = MyClass() +]] diff --git a/test/diagnostics/redefined-local.lua b/test/diagnostics/redefined-local.lua new file mode 100644 index 000000000..c594ed2ce --- /dev/null +++ b/test/diagnostics/redefined-local.lua @@ -0,0 +1,22 @@ +TEST [[ +local x +print(x) +local +print(x) +]] + +TEST [[ +local x +print(x) +local +print(x) +local +print(x) +]] + +TEST [[ +local x +return x, function () + return x +end +]] diff --git a/test/diagnostics/redundant-parameter.lua b/test/diagnostics/redundant-parameter.lua new file mode 100644 index 000000000..fabe33403 --- /dev/null +++ b/test/diagnostics/redundant-parameter.lua @@ -0,0 +1,214 @@ +TEST [[ +local function x(a, b) + return a, b +end +x(1, 2, ) +]] + +TEST [[ +local function x(a, b, ...) + return a, b, ... +end +x(1, 2, 3, 4, 5) +]] + +TEST [[ +---@type fun(a, b, ...) +local x +x(1, 2, 3, 4, 5) +]] + +TEST [[ +local m = {} +function m:x(a, b) + return a, b +end +m:x(1, 2, ) +]] + +TEST [[ +local m = {} +function m:x(a, b) + return a, b +end +m.x(m, 2, 3, ) +]] + +TEST [[ +local m = {} +function m.x(a, b) + return a, b +end +m:x(1, , , ) +]] + +TEST [[ +local m = {} +function m.x() +end +m:x() +]] + +TEST [[ +local function f(a, b) + return a, b +end +f(1, 2, , ) +]] + +TEST [[ +local mt = {} +function mt:f(a, b) + return a, b +end +mt.f(mt, 2, 3, ) +]] + +TEST [[ +local mt = {} +function mt.f(a, b) + return a, b +end +mt:f(1, , , ) +]] + +TEST [[ +local mt = {} +function mt:f(a, b) + return a, b +end +mt:f(1, 2, , ) +]] + +TEST [[ +local function f(a, b, ...) + return a, b, ... +end +f(1, 2, 3, 4) +]] + +TEST [[ +local _ = next({}, 1, ) +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() +--tostring = realTostring +--tostring(1) +--]] + +TEST [[ +local f = load('') +if f then + f(1, 2, 3) +end +]] + +TEST [[ +local m = {} +function m.open() +end + +m:open() +]] + +TEST [[ +local m = {} +function m:open() +end + +m.open(m) +]] + +TEST [[ +table.insert({}, 1, 2, ) +]] + +TEST [[ +---@overload fun(...) +local function f() end + +f(1) +]] + +TEST [[ +function F() end + +---@param x boolean +function F(x) end + +F(k()) +]] + +TEST [[ +local function f() + return 1, 2, 3 +end + +local function k() +end + +k() +]] + +TEST [[ +---@diagnostic disable: unused-local +local function f() + return 1, 2, 3 +end + +---@param x integer +local function k(x) +end + +k(f()) +]] + +TEST [[ +---@meta + +---@param x fun() +local function f1(x) +end + +---@return fun() +local function f2() +end + +f1(f2()) +]] + +TEST [[ +---@meta + +---@type fun():integer +local f + +---@param x integer +local function foo(x) end + +foo(f()) +]] + +TEST [[ +---@meta +---@diagnostic disable: duplicate-set-field +---@class A +local m = {} + +function m.ff() end + +function m.ff(x) end + +m.ff(1) +]] diff --git a/test/diagnostics/redundant-return-value.lua b/test/diagnostics/redundant-return-value.lua new file mode 100644 index 000000000..f3e2c584b --- /dev/null +++ b/test/diagnostics/redundant-return-value.lua @@ -0,0 +1,32 @@ +TEST [[ +---@type fun():number +local function f() + return 1, +end +]] + +TEST [[ +---@return number, number? +function F() + return 1, 1, +end +]] + +TEST [[ +---@return number, number? +function F() + return 1, 1, , , +end +]] + +TEST [[ +---@meta + +---@return number, number +local function r2() end + +---@return number, number? +function F() + return 1, +end +]] diff --git a/test/diagnostics/redundant-return.lua b/test/diagnostics/redundant-return.lua new file mode 100644 index 000000000..11214aab0 --- /dev/null +++ b/test/diagnostics/redundant-return.lua @@ -0,0 +1,34 @@ +TEST [[ +local function f() + +end +f() +]] + +TEST [[ +local function f() + return nil +end +f() +]] + +TEST [[ +local function f() + local function x() + + end + x() + return true +end +f() +]] + +TEST [[ +local function f() + local function x() + return true + end + return x() +end +f() +]] diff --git a/test/diagnostics/redundant-value.lua b/test/diagnostics/redundant-value.lua new file mode 100644 index 000000000..a63544df8 --- /dev/null +++ b/test/diagnostics/redundant-value.lua @@ -0,0 +1,7 @@ +TEST [[ +local _ = 1, +]] + +TEST [[ +_ = 1, +]] diff --git a/test/diagnostics/return-type-mismatch.lua b/test/diagnostics/return-type-mismatch.lua new file mode 100644 index 000000000..aaf7807f1 --- /dev/null +++ b/test/diagnostics/return-type-mismatch.lua @@ -0,0 +1,167 @@ +TEST [[ +---@return number +function F() + return +end +]] + +TEST [[ +---@return number? +function F() + return 1 +end +]] + +TEST [[ +---@return number? +function F() + return nil +end +]] + +TEST [[ +---@return number, number +local function f() + return 1, 1 +end + +---@return number, boolean +function F() + return +end +]] + +TEST [[ +---@return boolean, number +local function f() + return true, 1 +end + +---@return number, boolean +function F() + return +end +]] + +TEST [[ +---@return boolean, number? +local function f() + return true, 1 +end + +---@return number, boolean +function F() + return 1, f() +end +]] + +TEST [[ +---@return number, number? +local function f() + return 1, 1 +end + +---@return number, boolean, number +function F() + return 1, +end +]] + +TEST [[ +---@class A +---@field x number? + +---@return number +function F() + ---@type A + local t + return t.x +end +]] + +TEST [[ +---@class A +---@field x number? +local t = {} + +---@return number +function F() + return t.x +end +]] + +TEST [[ +---@param ... number +local function f(...) +end + +f(nil) +]] + +TEST [[ +---@return number +function F() + local n = 0 + if true then + n = 1 + end + return n +end +]] + +TEST [[ +---@param x boolean +---@return number +---@overload fun(): boolean +local function f(x) + if x then + return 1 + else + return false + end +end +]] + +TEST [[ +---@param x boolean +---@return number +---@overload fun() +local function f(x) + if x then + return 1 + else + return + end +end +]] + +TEST [[ +---@param x boolean +---@return number +---@overload fun() +local function f(x) + if x then + return 1 + end +end +]] + +TEST [[ +---@param x boolean +---@return number +---@overload fun(): boolean, boolean +local function f(x) + if x then + return 1 + else + return false, false + end +end +]] + +TEST [[ +---@type fun():number +local function f() + return +end +]] diff --git a/test/diagnostics/trailing-space.lua b/test/diagnostics/trailing-space.lua new file mode 100644 index 000000000..ff794714a --- /dev/null +++ b/test/diagnostics/trailing-space.lua @@ -0,0 +1,32 @@ +TEST [[ + +]] + +TEST [[ + + +]] + +TEST [[ +X = 1 +]] + +TEST [[ +X = [=[ + ]=] +]] + +TEST [[ +-- xxxx +]] + +TEST [[ +-- [=[ + ]=] +]] + +TEST [=[ +return [[ + +]] +]=] diff --git a/test/diagnostics/type-check.lua b/test/diagnostics/type-check.lua deleted file mode 100644 index 3aeca5d5a..000000000 --- a/test/diagnostics/type-check.lua +++ /dev/null @@ -1,781 +0,0 @@ -local config = require 'config' - -config.add(nil, 'Lua.diagnostics.disable', 'unused-local') -config.add(nil, 'Lua.diagnostics.disable', 'unused-function') -config.add(nil, 'Lua.diagnostics.disable', 'undefined-global') -config.set(nil, 'Lua.type.castNumberToInteger', false) - -TEST [[ -local x = 0 - - = true -]] - -TEST [[ ----@type integer -local x - - = true -]] - -TEST [[ ----@type unknown -local x - -x = nil -]] - -TEST [[ ----@type unknown -local x - -x = 1 -]] - -TEST [[ ----@type unknown|nil -local x - -x = 1 -]] - -TEST [[ -local x = {} - -x = nil -]] - -TEST [[ ----@type string -local x - - = nil -]] - -TEST [[ ----@type string? -local x - -x = nil -]] - -TEST [[ ----@type table -local x - - = nil -]] - -TEST [[ -local x - -x = nil -]] - -TEST [[ ----@type integer -local x - ----@type number - = f() -]] - -TEST [[ ----@type number -local x - ----@type integer -x = f() -]] - -TEST [[ ----@type number|boolean -local x - ----@type string - = f() -]] - -TEST [[ ----@type number|boolean -local x - ----@type boolean -x = f() -]] - -TEST [[ ----@type number|boolean -local x - ----@type boolean|string - = f() -]] - -TEST [[ ----@type boolean -local x - -if not x then - return -end - -x = f() -]] - -TEST [[ ----@type boolean -local x - ----@type integer -local y - - = y -]] - -TEST [[ -local y = true - -local x -x = 1 -x = y -]] - -TEST [[ -local t = {} - -local x = 0 -x = x + #t -]] - -TEST [[ -local x = 0 - -x = 1.0 -]] - -TEST [[ ----@class A - -local t = {} - ----@type A -local a - -t = a -]] - -TEST [[ -local m = {} - ----@type integer[] -m.ints = {} -]] - -TEST [[ ----@class A ----@field x A - ----@type A -local t - -t.x = {} -]] - -TEST [[ ----@class A ----@field x integer - ----@type A -local t - - = true -]] - -TEST [[ ----@class A ----@field x integer - ----@type A -local t - ----@type boolean -local y - - = y -]] - -TEST [[ ----@class A -local m - -m.x = 1 - ----@type A -local t - - = true -]] - -TEST [[ ----@class A -local m - ----@type integer -m.x = 1 - - = true -]] - -TEST [[ ----@class A -local mt - ----@type integer -mt.x = 1 - -function mt:init() - = true -end -]] - -TEST [[ ----@class A ----@field x integer - ----@type A -local t = { - = true -} -]] - -TEST [[ ----@type boolean[] -local t = {} - -t[5] = nil -]] - -TEST [[ ----@type table -local t = {} - -t['x'] = nil -]] - -TEST [[ -local t = { true } - -t[1] = nil -]] - -TEST [[ ----@class A -local t = { - x = 1 -} - - = true -]] - -TEST [[ ----@type number -local t - -t = 1 -]] - -TEST [[ ----@type number -local t - ----@type integer -local y - -t = y -]] - -TEST [[ ----@class A -local m - ----@type number -m.x = 1 - - = {} -]] - -TEST [[ ----@param x number -local function f(x) end - -f() -]] - -TEST [[ ----@type integer -local x - -x = 1.0 -]] - -TEST [[ ----@type integer -local x - - = 1.5 -]] - -TEST [[ ----@diagnostic disable:undefined-global ----@type integer -local x - -x = 1 + G -]] - -TEST [[ ----@diagnostic disable:undefined-global ----@type integer -local x - -x = 1 + G -]] - -TEST [[ ----@alias A integer - ----@type A -local a - ----@type integer -local b - -b = a -]] - -TEST [[ ----@type string|boolean -local t - ----@cast t string -]] - -TEST [[ ----@type string|boolean -local t - ----@cast t -]] - -TEST [[ -local n - -if G then - n = {} -else - n = nil -end - -local t = { - x = n, -} -]] - -TEST [[ ----@class A - ----@param n A -local function f(n) -end - ----@class B -local a = {} - ----@type A? -a.x = XX - -f(a.x) -]] - -TEST [[ ----@type string? -local x - -local s = :upper() -]] - -TEST [[ ----@alias A string|boolean - ----@param x string|boolean -local function f(x) end - ----@type A -local x - -f(x) -]] - -TEST [[ ----@alias A string|boolean - ----@param x A -local function f(x) end - ----@type string|boolean -local x - -f(x) -]] - -TEST [[ ----@type boolean[] -local t = {} - ----@type boolean? -local x - -t[#t+1] = x -]] - -TEST [[ ----@type number -local n ----@type integer -local i - - = n -]] - -config.set(nil, 'Lua.type.castNumberToInteger', true) -TEST [[ ----@type number -local n ----@type integer -local i - -i = n -]] -config.set(nil, 'Lua.type.castNumberToInteger', false) - -TEST [[ ----@type number|boolean -local nb - ----@type number -local n - - = nb -]] - -config.set(nil, 'Lua.type.weakUnionCheck', true) -TEST [[ ----@type number|boolean -local nb - ----@type number -local n - -n = nb -]] -config.set(nil, 'Lua.type.weakUnionCheck', false) - -TEST [[ ----@class Option: string - ----@param x Option -local function f(x) end - ----@type Option -local x = 'aaa' - -f(x) -]] - -TEST [[ ----@type number -local = 'aaa' -]] - -TEST [[ ----@return number -function F() - return -end -]] - -TEST [[ ----@return number? -function F() - return 1 -end -]] - -TEST [[ ----@return number? -function F() - return nil -end -]] - -TEST [[ ----@return number, number -local function f() end - ----@return number, boolean -function F() - return -end -]] - -TEST [[ ----@return boolean, number -local function f() end - ----@return number, boolean -function F() - return -end -]] - -TEST [[ ----@return boolean, number? -local function f() end - ----@return number, boolean -function F() - return 1, f() -end -]] - -TEST [[ ----@return number, number? -local function f() end - ----@return number, boolean, number -function F() - return 1, -end -]] - -TEST [[ ----@class A ----@field x number? - ----@return number -function F() - ---@type A - local t - return t.x -end -]] - -TEST [[ ----@class A ----@field x number? -local t = {} - ----@return number -function F() - return t.x -end -]] - -TEST [[ ----@param ... number -local function f(...) -end - -f(nil) -]] - -TEST [[ ----@return number -function F() - local n = 0 - if true then - n = 1 - end - return n -end -]] - -TEST [[ ----@class X - ----@class A -local mt = G - ----@type X -mt._x = nil -]] - -config.set(nil, 'Lua.type.weakNilCheck', true) -TEST [[ ----@type number? -local nb - ----@type number -local n - -n = nb -]] - -TEST [[ ----@type number|nil -local nb - ----@type number -local n - -n = nb -]] -config.set(nil, 'Lua.type.weakNilCheck', false) - -TEST [[ ----@class A -local a = {} - ----@class B -local = a -]] - -TEST [[ ----@class A -local a = {} - ----@class B: A -local b = a -]] - -TEST [[ ----@class A -local a = {} -a.__index = a - ----@class B: A -local b = setmetatable({}, a) -]] - -TEST [[ ----@class A -local a = {} - ----@class B: A -local b = setmetatable({}, {__index = a}) -]] - -TEST [[ ----@class A -local a = {} - ----@class B -local = setmetatable({}, {__index = a}) -]] - -TEST [[ ----@class A ----@field x number? -local a - ----@class B ----@field x number -local b - -b.x = a.x -]] - -TEST [[ - ----@class A ----@field x number? -local a - ----@type number -local t - -t = a.x -]] - -TEST [[ -local mt = {} -mt.x = 1 -mt.x = nil -]] - -TEST [[ ----@type string[] -local t - - = 'xxx' -]] - -TEST [[ ----@param b boolean -local function f(b) -end - ----@type boolean -local t - -if t then - f(t) -end -]] - -config.set(nil, 'Lua.type.weakUnionCheck', true) -TEST [[ ----@type number -local x = G -]] - -TEST [[ ----@generic T ----@param x T ----@return T -local function f(x) - return x -end -]] -config.set(nil, 'Lua.type.weakUnionCheck', false) - -TEST [[ ----@type 1|2 -local x - -x = 1 -x = 2 - = 3 -]] - -TEST [[ ----@type 'x'|'y' -local x - -x = 'x' -x = 'y' - = 'z' -]] - -TEST [[ ----@enum A -local t = { - x = 1, - y = 2, -} - ----@param x A -local function f(x) -end - -f() -f(t.x) -f(1) -f() -]] - -config.remove(nil, 'Lua.diagnostics.disable', 'unused-local') -config.remove(nil, 'Lua.diagnostics.disable', 'unused-function') -config.remove(nil, 'Lua.diagnostics.disable', 'undefined-global') -config.set(nil, 'Lua.type.castNumberToInteger', true) diff --git a/test/diagnostics/unbalanced-assignments.lua b/test/diagnostics/unbalanced-assignments.lua new file mode 100644 index 000000000..a0d47f569 --- /dev/null +++ b/test/diagnostics/unbalanced-assignments.lua @@ -0,0 +1,34 @@ +TEST [[ +local x, , = 1 +]] + +TEST [[ +local x, y, = 1, 2 +]] + +TEST [[ +local x, y, z = print() +]] + +TEST [[ +local x, y, z +]] + +TEST [[ +local x, y, z +x, , = 1 +]] + +TEST [[ +X, , = 1 +]] + +TEST [[ +T = {} +T.x, , = 1 +]] + +TEST [[ +T = {} +T['x'], , = 1 +]] diff --git a/test/diagnostics/undefined-doc-class.lua b/test/diagnostics/undefined-doc-class.lua new file mode 100644 index 000000000..d10b89d81 --- /dev/null +++ b/test/diagnostics/undefined-doc-class.lua @@ -0,0 +1,3 @@ +TEST [[ +---@class A : +]] diff --git a/test/diagnostics/undefined-doc-name.lua b/test/diagnostics/undefined-doc-name.lua new file mode 100644 index 000000000..9a55108ac --- /dev/null +++ b/test/diagnostics/undefined-doc-name.lua @@ -0,0 +1,19 @@ +TEST [[ +---@type +]] + +TEST [[ +---@class A +---@type A|| +]] + +TEST [[ +---@class AAA +---@alias B AAA + +---@type B +]] + +TEST [[ +---@alias B +]] diff --git a/test/diagnostics/undefined-doc-param.lua b/test/diagnostics/undefined-doc-param.lua new file mode 100644 index 000000000..7bfbafa56 --- /dev/null +++ b/test/diagnostics/undefined-doc-param.lua @@ -0,0 +1,21 @@ +TEST [[ +---@param Class +]] + +TEST [[ +---@class Class +---@param Class +local function f(x) + return x +end +f() +]] + +TEST [[ +---@class Class +---@param Class +function F(x) + return x +end +F() +]] diff --git a/test/diagnostics/undefined-env-child.lua b/test/diagnostics/undefined-env-child.lua new file mode 100644 index 000000000..73904a747 --- /dev/null +++ b/test/diagnostics/undefined-env-child.lua @@ -0,0 +1,36 @@ +TEST [[ +---@type iolib +_ENV = io +(stderr) -- `print` is warning but `stderr` is not +]] + +TEST [[ +---@type iolib +local _ENV = io +(stderr) -- `print` is warning but `stderr` is not +]] + +TEST [[ +local _ENV = { print = print } +print(1) +]] + +TEST [[ +_ENV = {} +GLOBAL = 1 --> _ENV.GLOBAL = 1 +]] + +TEST [[ +_ENV = {} +local _ = print --> local _ = _ENV.print +]] + +TEST [[ +GLOBAL = 1 +_ENV = nil +]] + +TEST [[ +print(1) +_ENV = nil +]] diff --git a/test/diagnostics/undefined-field.lua b/test/diagnostics/undefined-field.lua new file mode 100644 index 000000000..aff329fbe --- /dev/null +++ b/test/diagnostics/undefined-field.lua @@ -0,0 +1,148 @@ +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. + 1) +print(v:method1()) +print(v.method2()) +print(v:()) + +---@type Bar +local v2 +print(v2.field1 + 1) +print(v2.field2 + 1) +print(v2. + 1) +print(v2.field4 + 1) +print(v2:method1()) +print(v2.method2()) +print(v2:()) + +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:() -- 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:() -- doc.class.name +v:method3() +]] + +-- checkUndefinedField ็ฑปๅๅ’Œ็ฑปๅ˜้‡ๅŒๅ๏ผŒ็ฑปๅ˜้‡่ขซ็›ดๆŽฅไฝฟ็”จ +TEST [[ +---@class Foo +local Foo +function Foo:method1() end +Foo:() -- doc.class +Foo:() -- 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.() -- doc.class + self:method1() + return self.() -- 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 +v2:method1() +v2:() +]] + +TEST [[ +---@type table +T1 = {} +print(T1.f1) +---@type tablelib +T2 = {} +print(T2.) +]] + +TEST [[ +---@type string|table +local n + +print(n.x) +]] + +TEST [[ +---@type 'x' +local t + +local n = t:upper() +]] + +TEST [[ +---@alias A 'x' + +---@type A +local t + +local n = t:upper() +]] diff --git a/test/diagnostics/undefined-global.lua b/test/diagnostics/undefined-global.lua new file mode 100644 index 000000000..f5a6396c3 --- /dev/null +++ b/test/diagnostics/undefined-global.lua @@ -0,0 +1,36 @@ +TEST [[ +local print, _G +print() +print() +print() +print() +print() +print(Z) +print(_G) +Z = 1 +]] + +TEST [[ +X = table[] +]] +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 [[ +---@class c +c = {} +]] diff --git a/test/diagnostics/unknown-cast-variable.lua b/test/diagnostics/unknown-cast-variable.lua new file mode 100644 index 000000000..a347083f4 --- /dev/null +++ b/test/diagnostics/unknown-cast-variable.lua @@ -0,0 +1,8 @@ +TEST [[ +---@cast integer +]] + +TEST [[ +local x, y +---@cast y number +]] diff --git a/test/diagnostics/unknown-diag-code.lua b/test/diagnostics/unknown-diag-code.lua new file mode 100644 index 000000000..33b418861 --- /dev/null +++ b/test/diagnostics/unknown-diag-code.lua @@ -0,0 +1,3 @@ +TEST [[ +---@diagnostic disable-next-line: +]] diff --git a/test/diagnostics/unknown-operator.lua b/test/diagnostics/unknown-operator.lua new file mode 100644 index 000000000..bb193f6aa --- /dev/null +++ b/test/diagnostics/unknown-operator.lua @@ -0,0 +1,4 @@ +TEST [[ +---@class A +---@operator : A +]] diff --git a/test/diagnostics/unreachable-code.lua b/test/diagnostics/unreachable-code.lua new file mode 100644 index 000000000..4444252f5 --- /dev/null +++ b/test/diagnostics/unreachable-code.lua @@ -0,0 +1,71 @@ +TEST [[ +if X then + return false +elseif X then + return false +else + return false +end + +]] + +TEST [[ +function X() + if X then + return false + elseif X then + return false + else + return false + end + +end +]] + +TEST [[ +while true do +end + + +]] + +TEST [[ +while true do +end + + +]] + +TEST [[ +while X do + X = 1 +end + +print(1) +]] + +TEST [[ +while true do + if not X then + break + end +end + +print(1) + +do return end +]] + +TEST [[ +local done = false + +local function set_done() + done = true +end + +while not done do + set_done() +end + +print(1) +]] diff --git a/test/diagnostics/unused-function.lua b/test/diagnostics/unused-function.lua new file mode 100644 index 000000000..c2cea23ab --- /dev/null +++ b/test/diagnostics/unused-function.lua @@ -0,0 +1,48 @@ +TEST [[ +local +]] + +TEST [[ +local x = +]] + +TEST [[ +local x +x = +]] + +TEST [[ +local +local +]] + +TEST [[ +local f = +]] + +TEST [[ +local f;f = +]] + +TEST [[ +local +]] + +TEST [[ +local +]] + + +TEST [[ +local + +local +]] diff --git a/test/diagnostics/unused-label.lua b/test/diagnostics/unused-label.lua new file mode 100644 index 000000000..3a89a1474 --- /dev/null +++ b/test/diagnostics/unused-label.lua @@ -0,0 +1,3 @@ +TEST [[ +:::: +]] diff --git a/test/diagnostics/unused-local.lua b/test/diagnostics/unused-local.lua new file mode 100644 index 000000000..425f3e1e8 --- /dev/null +++ b/test/diagnostics/unused-local.lua @@ -0,0 +1,99 @@ +local config = require 'config' + +TEST [[ +local +]] + +TEST [[ +local y +local x = y +]] + +TEST [[ +local function x() +end +x() +]] + +TEST [[ +return function (x) + x.a = 1 +end +]] + +TEST [[ +local = {} +.a = 1 +]] + +TEST [[ +InstanceName = 1 +Instance = _G[InstanceName] +]] + +TEST [[ +local _ = (''):sub(1, 2) +]] + +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 = {} +function :f() +end +]] + +TEST [[ +local = {} +.a = 1 +]] + +TEST [[ +local = {} +['a'] = 1 +]] + +TEST [[ +local function f() + return 'something' +end +f() +]] + +TEST [[ +local function f(var) + print(var) +end +local var +f(var) +]] + +TEST [[ +local = {} +[1] = 1 +]] + +config.add(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*') + +TEST [[ +local +local ll_1 +local ll_2 +local +]] + +config.remove(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*') diff --git a/test/diagnostics/unused-vararg.lua b/test/diagnostics/unused-vararg.lua new file mode 100644 index 000000000..2bed4aabe --- /dev/null +++ b/test/diagnostics/unused-vararg.lua @@ -0,0 +1,6 @@ +TEST [[ +local function f() + return 'something' +end +f() +]] diff --git a/test/document_symbol/init.lua b/test/document_symbol/init.lua index 4ce755738..53e0dab2a 100644 --- a/test/document_symbol/init.lua +++ b/test/document_symbol/init.lua @@ -48,11 +48,12 @@ end ---@diagnostic disable: await-in-sync function TEST(script) return function (expect) - files.setText('', script) - local result = core('') + files.setText(TESTURI, script) + local result = core(TESTURI) + assert(result) assert(eq(expect, result)) checkArcoss(result) - files.remove('') + files.remove(TESTURI) end end @@ -62,8 +63,8 @@ A = 1 { [1] = { name = 'A', - detail = 'global number = 1', - kind = define.SymbolKind.Class, + detail = '1', + kind = define.SymbolKind.Number, range = {0, 5}, selectionRange = {0, 1}, } @@ -105,8 +106,8 @@ end ]] { [1] = { - name = '', - detail = 'return function ()', + name = 'return', + detail = 'function ()', kind = define.SymbolKind.Function, range = {7, 10003}, selectionRange = {7, 15}, @@ -219,8 +220,8 @@ local z { [1] = { name = 'x', - detail = 'local number = 1', - kind = define.SymbolKind.Variable, + detail = '1', + kind = define.SymbolKind.Number, range = {6, 11}, selectionRange = {6, 7}, }, @@ -234,15 +235,15 @@ local z children = { [1] = { name = 'x', - detail = 'local string = "x"', - kind = define.SymbolKind.Variable, + detail = '"x"', + kind = define.SymbolKind.String, range = {20010, 20017}, selectionRange = {20010, 20011}, }, [2] = { name = 'y', - detail = 'local {}', - kind = define.SymbolKind.Variable, + detail = '', + kind = define.SymbolKind.Object, range = {30010, 30016}, selectionRange = {30010, 30011}, valueRange = {30014, 30016}, @@ -259,14 +260,14 @@ local z }, [3] = { name = 'y', - detail = 'local boolean = true', - kind = define.SymbolKind.Variable, + detail = 'true', + kind = define.SymbolKind.Boolean, range = {60006, 60014}, selectionRange = {60006, 60007}, }, [4] = { name = 'z', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {70006, 70007}, selectionRange = {70006, 70007}, @@ -283,30 +284,30 @@ local t = { { [1] = { name = 't', - detail = 'local {a, b, c}', - kind = define.SymbolKind.Variable, + detail = '{a, b, c}', + kind = define.SymbolKind.Object, range = {6, 40001}, selectionRange = {6, 7}, valueRange = {10, 40001}, children = { [1] = { name = 'a', - detail = 'field number = 1', - kind = define.SymbolKind.Property, + detail = '1', + kind = define.SymbolKind.Number, range = {10004, 10009}, selectionRange = {10004, 10005}, }, [2] = { name = 'b', - detail = 'field number = 2', - kind = define.SymbolKind.Property, + detail = '2', + kind = define.SymbolKind.Number, range = {20004, 20009}, selectionRange = {20004, 20005}, }, [3] = { name = 'c', - detail = 'field number = 3', - kind = define.SymbolKind.Property, + detail = '3', + kind = define.SymbolKind.Number, range = {30004, 30009}, selectionRange = {30004, 30005}, }, @@ -314,6 +315,28 @@ local t = { } } +TEST [[ +local t = { + a = 1, + b = 2, + c = 3, + d = 4, + e = 5, + f = 6, +} +]] +{ + [1] = { + name = 't', + detail = '{a, b, c, d, e, ...(+1)}', + kind = define.SymbolKind.Object, + range = {6, 70001}, + selectionRange = {6, 7}, + valueRange = {10, 70001}, + children = EXISTS, + } +} + TEST [[ local t = { a = { @@ -324,24 +347,24 @@ local t = { { [1] = { name = 't', - detail = 'local {a}', - kind = define.SymbolKind.Variable, + detail = '{a}', + kind = define.SymbolKind.Object, range = {6, 40001}, selectionRange = {6, 7}, valueRange = {10, 40001}, children = { [1] = { name = 'a', - detail = 'field {b}', - kind = define.SymbolKind.Property, + detail = '{b}', + kind = define.SymbolKind.Object, range = {10004, 30005}, selectionRange = {10004, 10005}, valueRange = {10008, 30005}, children = { [1] = { name = 'b', - detail = EXISTS, - kind = define.SymbolKind.Property, + detail = '1', + kind = define.SymbolKind.Number, range = {20008, 20013}, selectionRange = {20008, 20009}, } @@ -367,8 +390,8 @@ g = 1 }, [2] = { name = 'g', - detail = 'setlocal number = 1', - kind = define.SymbolKind.Variable, + detail = '1', + kind = define.SymbolKind.Number, range = {30000, 30005}, selectionRange = {30000, 30001}, } @@ -390,21 +413,21 @@ end children = { [1] = { name = 'a', - detail = 'param', + detail = '', kind = define.SymbolKind.Constant, range = {11, 12}, selectionRange = {11, 12}, }, [2] = { name = 'b', - detail = 'param', + detail = '', kind = define.SymbolKind.Constant, range = {14, 15}, selectionRange = {14, 15}, }, [3] = { name = 'x', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {10010, 10017}, selectionRange = {10010, 10011}, @@ -423,8 +446,8 @@ local v = t ]]{ [1] = { name = 't', - detail = 'local {a, b}', - kind = define.SymbolKind.Variable, + detail = '{a, b}', + kind = define.SymbolKind.Object, range = {6, 30001}, selectionRange = {6, 7}, valueRange = {10, 30001}, @@ -432,7 +455,7 @@ local v = t }, [2] = { name = 'v', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {50006, 50011}, selectionRange = {50006, 50007}, @@ -445,7 +468,7 @@ local function ]]{ [1] = { name = 'x', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {6, 7}, selectionRange = {6, 7}, @@ -453,7 +476,7 @@ local function [2] = { name = "", detail = "function ()", - kind = 12, + kind = define.SymbolKind.Function, range = {10006, 10014}, selectionRange = {10006, 10014}, valueRange = {10006, 10014}, @@ -474,8 +497,8 @@ local a, b = { ]]{ [1] = { name = 'a', - detail = 'local {x1, y1, z1}', - kind = define.SymbolKind.Variable, + detail = '{x1, y1, z1}', + kind = define.SymbolKind.Object, range = {6, 40001}, selectionRange = {6, 7}, valueRange = {13, 40001}, @@ -483,8 +506,8 @@ local a, b = { }, [2] = { name = 'b', - detail = 'local {x2, y2, z2}', - kind = define.SymbolKind.Variable, + detail = '{x2, y2, z2}', + kind = define.SymbolKind.Object, range = {9, 80001}, selectionRange = {9, 10}, valueRange = {40003, 80001}, @@ -519,7 +542,7 @@ end children = { [1] = { name = 'c', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {40010, 40011}, selectionRange = {40010, 40011}, @@ -536,18 +559,28 @@ local t = f({ { [1] = { name = 't', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {6, 20002}, selectionRange = {6, 7}, valueRange = {10, 20002}, children = { [1] = { - name = 'k', - detail = 'field number = 1', - kind = define.SymbolKind.Property, - range = {10004, 10009}, - selectionRange = {10004, 10005}, + name = 'f', + detail = '-> {k}', + kind = define.SymbolKind.Object, + range = {12, 20001}, + selectionRange = {12, 20001}, + valueRange = {12, 20001}, + children = { + [1] = { + name = 'k', + detail = '1', + kind = define.SymbolKind.Number, + range = {10004, 10009}, + selectionRange = {10004, 10005}, + } + } } } } @@ -562,8 +595,8 @@ end { [1] = { name = 't', - detail = 'local {}', - kind = define.SymbolKind.Variable, + detail = '', + kind = define.SymbolKind.Object, range = {6, 12}, selectionRange = {6, 7}, valueRange = {10, 12}, @@ -578,14 +611,14 @@ end children = { [1] = { name = 'a', - detail = 'param', + detail = '', kind = define.SymbolKind.Constant, range = {20017, 20018}, selectionRange = {20017, 20018}, }, [2] = { name = 'b', - detail = 'param', + detail = '', kind = define.SymbolKind.Constant, range = {20020, 20021}, selectionRange = {20020, 20021}, @@ -603,19 +636,29 @@ local a = f { { [1] = { name = 'a', - detail = 'local', + detail = '', kind = define.SymbolKind.Variable, range = {6, 30001}, selectionRange = {6, 7}, valueRange = {10, 30001}, children = { [1] = { - name = 'x', - detail = 'function ()', - kind = define.SymbolKind.Function, - range = {10004, 20007}, - selectionRange = {10004, 10005}, - valueRange = {10008, 20007}, + name = 'f', + detail = '-> {x}', + kind = define.SymbolKind.Object, + range = {12, 30001}, + selectionRange = {12, 30001}, + valueRange = {12, 30001}, + children = { + [1] = { + name = 'x', + detail = 'function ()', + kind = define.SymbolKind.Function, + range = {10004, 20007}, + selectionRange = {10004, 10005}, + valueRange = {10008, 20007}, + } + } } } } @@ -628,8 +671,8 @@ end) ]] { [1] = { - name = '', - detail = 'table.sort -> function (a, b)', + name = 'table.sort', + detail = '-> function (a, b)', kind = define.SymbolKind.Function, range = {14, 20003}, selectionRange = {14, 22}, @@ -649,8 +692,8 @@ local root = { { [1] = { name = 'root', - detail = 'local {inner_function}', - kind = define.SymbolKind.Variable, + detail = '{inner_function}', + kind = define.SymbolKind.Object, range = {6, 50001}, selectionRange = {6, 10}, valueRange = {13, 50001}, @@ -676,3 +719,158 @@ local root = { }, } } + +TEST [[ +local t = { 1, 2, 3 } +]] +{ + [1] = { + name = 't', + detail = '[1, 2, 3]', + kind = define.SymbolKind.Array, + range = {6, 21}, + selectionRange = {6, 7}, + valueRange = {10, 21}, + children = EXISTS + } +} + +TEST [[ +local t = { 1, 2, 3, 4, 5, 6 } +]] +{ + [1] = { + name = 't', + detail = '[1, 2, 3, 4, 5, ...(+1)]', + kind = define.SymbolKind.Array, + range = {6, 30}, + selectionRange = {6, 7}, + valueRange = {10, 30}, + children = EXISTS, + } +} + +TEST [[ +local t = { 1, 2, [5] = 3, [true] = 4, x = 5 } +]] +{ + [1] = { + name = 't', + detail = '{[1], [2], [5], [true], x}', + kind = define.SymbolKind.Object, + range = {6, 46}, + selectionRange = {6, 7}, + valueRange = {10, 46}, + children = EXISTS + } +} + +TEST [[ +if 1 then + +elseif 2 then + +elseif 3 then + +else + +end +]] +{ + { + name = 'if', + detail = 'if 1 then', + kind = define.SymbolKind.Package, + range = {0, 20000}, + selectionRange = {0, 2}, + valueRange = {0, 20000}, + }, + { + name = 'elseif', + detail = 'elseif 2 then', + kind = define.SymbolKind.Package, + range = {20000, 40000}, + selectionRange = {20000, 20006}, + valueRange = {20000, 40000}, + }, + { + name = 'elseif', + detail = 'elseif 3 then', + kind = define.SymbolKind.Package, + range = {40000, 60000}, + selectionRange = {40000, 40006}, + valueRange = {40000, 60000}, + }, + { + name = 'else', + detail = 'else', + kind = define.SymbolKind.Package, + range = {60000, 80000}, + selectionRange = {60000, 60004}, + valueRange = {60000, 80000}, + }, +} + +TEST [[ +while true do + +end +]] +{ + { + name = 'while', + detail = 'while true do', + kind = define.SymbolKind.Package, + range = {0, 20003}, + selectionRange = {0, 5}, + valueRange = {0, 20003}, + }, +} + +TEST [[ +repeat + +until true +]] +{ + { + name = 'repeat', + detail = 'until true', + kind = define.SymbolKind.Package, + range = {0, 20010}, + selectionRange = {0, 6}, + valueRange = {0, 20010}, + }, +} + +TEST [[ +for i = 1, 10 do + +end +]] +{ + { + name = 'for', + detail = 'for i = 1, 10 do', + kind = define.SymbolKind.Package, + range = {0, 20003}, + selectionRange = {0, 3}, + valueRange = {0, 20003}, + children = EXISTS, + }, +} + +TEST [[ +X +.Y +.Z = 1 +]] +{ + { + name = '... .Z', + detail = '1', + kind = define.SymbolKind.Number, + range = {20001, 20006}, + selectionRange = {20001, 20002}, + }, +} diff --git a/test/full/example.lua b/test/full/example.lua index 7740d23c9..920dcd0d8 100644 --- a/test/full/example.lua +++ b/test/full/example.lua @@ -4,7 +4,7 @@ local files = require 'files' local diag = require 'core.diagnostics' local config = require 'config' local fs = require 'bee.filesystem' -local luadoc = require "parser.luadoc" +local luadoc = require "parser".luadoc -- ไธดๆ—ถ ---@diagnostic disable: await-in-sync @@ -51,15 +51,15 @@ local function testIfExit(path) local max = 100 local need for i = 1, max do - files.open('') - files.setText('', buf) - diag('', false, function () end) + files.open(TESTURI) + files.setText(TESTURI, buf) + diag(TESTURI, false, function () end) local passed = os.clock() - clock if passed >= 1.0 or i == max then need = passed / i break end - files.remove('') + files.remove(TESTURI) end print(('ๅŸบๅ‡†่ฏŠๆ–ญๆต‹่ฏ•[%s]ๅ•ๆฌก่€—ๆ—ถ๏ผš%.10f'):format(path:filename():string(), need)) end diff --git a/test/full/init.lua b/test/full/init.lua index ac78f1341..a6496a99b 100644 --- a/test/full/init.lua +++ b/test/full/init.lua @@ -37,7 +37,7 @@ end) do end end -util.revertTable(times) +util.revertArray(times) for _, time in ipairs(times) do print(time) end diff --git a/test/full/projects.lua b/test/full/projects.lua index dacc101c0..20ef67243 100644 --- a/test/full/projects.lua +++ b/test/full/projects.lua @@ -41,7 +41,7 @@ local function doProjects(pathname) print('ๅผ€ๅง‹่ฏŠๆ–ญ...') - ws.ready = true + furi.encode(path:string()) diag.diagnosticsScope(furi.encode(path:string())) local clock = os.clock() diff --git a/test/full/self.lua b/test/full/self.lua index 09d2d154f..d118e0345 100644 --- a/test/full/self.lua +++ b/test/full/self.lua @@ -32,7 +32,7 @@ end print('ๅŸบๅ‡†่ฏŠๆ–ญ็›ฎๅฝ•๏ผš', path) -ws.ready = true +ws.awaitReady(furi.encode(path:string())) diag.diagnosticsScope(furi.encode(path:string())) local clock = os.clock() @@ -86,7 +86,7 @@ end) do end end -util.revertTable(printTexts) +util.revertArray(printTexts) for _, text in ipairs(printTexts) do print(text) diff --git a/test/highlight/init.lua b/test/highlight/init.lua index 0cca79f14..4baea6f6c 100644 --- a/test/highlight/init.lua +++ b/test/highlight/init.lua @@ -21,18 +21,19 @@ end function TEST(script) local newScript, catched = catch(script, '!') - files.setText('', newScript) + files.setText(TESTURI, newScript) for _, enter in ipairs(catched['!']) do local start, finish = enter[1], enter[2] local pos = (start + finish) // 2 - local positions = core('', pos) + local positions = core(TESTURI, pos) + assert(positions) local results = {} for _, position in ipairs(positions) do results[#results+1] = { position.start, position.finish } end assert(founded(catched['!'], results)) end - files.remove('') + files.remove(TESTURI) end TEST [[ @@ -140,3 +141,16 @@ TEST [[ = 1 TEST2 = 2 ]] + +TEST [[ +local zing = foo +local foo = 4 + +local bar = { + = foo +} + +bar. = 5 + +print(bar.) +]] diff --git a/test/hover/init.lua b/test/hover/init.lua index 40474bf5a..0cfce3a42 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -5,33 +5,17 @@ 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, - ['doc.class.name'] = true, - ['function'] = true, -} - ---@diagnostic disable: await-in-sync function TEST(script) return function (expect) local newScript, catched = catch(script, '?') - files.setText('', newScript) - local hover = core.byUri('', catched['?'][1][1]) + files.setText(TESTURI, newScript) + local hover = core.byUri(TESTURI, catched['?'][1][1]) assert(hover) expect = expect:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') local label = hover:string():gsub('\r\n', '\n'):match('```lua[\r\n]*(.-)[\r\n]*```') assert(expect == label) - files.remove('') + files.remove(TESTURI) end end @@ -254,14 +238,14 @@ TEST [[ string.() ]] [[ -function string.sub(s: string, i: integer, j?: integer) +function string.sub(s: string|number, i: integer, j?: integer) -> string ]] TEST[[ ('xx'):() ]] -[[function string.sub(s: string, i: integer, j?: integer) +[[function string.sub(s: string|number, i: integer, j?: integer) -> string]] TEST [[ @@ -288,7 +272,7 @@ TEST [[ string.() ]] [[ -function string.lower(s: string) +function string.lower(s: string|number) -> string ]] @@ -312,7 +296,6 @@ end ]] [[ function x() - -> unknown ]] TEST [[ @@ -437,7 +420,7 @@ local t: { [1]: integer = 2, [true]: integer = 3, [5.5]: integer = 4, - b: integer = 7, + ["b"]: integer = 7, ["012"]: integer = 8, } ]] @@ -507,7 +490,7 @@ local self: { id: integer = 1, remove: function, __index: table, - __name: string = "obj", + __name: string = 'obj', } ]] @@ -627,7 +610,7 @@ end local = a(1) ]] [[ -local r: string = "a" +local r: string = 'a' ]] TEST[[ @@ -637,7 +620,7 @@ end local _, = pcall(a, 1) ]] [[ -local r: string = "a" +local r: string = 'a' ]] TEST[[ @@ -650,7 +633,7 @@ local n: integer TEST[[ local = '\a' ]] -[[local x: string = "\007"]] +[[local x: string = '\007']] TEST [[ local = { @@ -844,9 +827,9 @@ local = { ]] [[ local t: { - [1]: string = "aaa", - [2]: string = "bbb", - [3]: string = "ccc", + [1]: string = 'aaa', + [2]: string = 'bbb', + [3]: string = 'ccc', } ]] @@ -1245,6 +1228,17 @@ local local x: table ]] +TEST [[ +---@type [ClassA, ClassB] +local +]] +[[ +local x: [ClassA, ClassB] { + [1]: ClassA, + [2]: ClassB, +} +]] + --TEST [[ -----@class ClassA -----@class ClassB @@ -1685,7 +1679,7 @@ TEST [[ local ]] [[ -local t: { +local t: { x: string, y: number, z: boolean } { x: string, y: number, z: boolean, @@ -1729,13 +1723,13 @@ local = { ]] [[ local t: { - x: string = "e", - y: string = "f", - z: string = "g", - [1]: string = "a", - [2]: string = "b", - [3]: string = "c"|"h", - [10]: string = "d", + x: string = 'e', + y: string = 'f', + ['z']: string = 'g', + [10]: string = 'd', + [1]: string = 'a', + [2]: string = 'b', + [3]: string = 'c'|'h', } ]] @@ -1847,7 +1841,7 @@ local t = { local = t[#t] ]] [[ -local x: string = "x" +local x: string = 'x' ]] TEST [[ @@ -1881,10 +1875,10 @@ local = { ]] [[ local x: { - x: string = "", - y: string = "", - _x: string = "", - _y: string = "", + x: string = '', + y: string = '', + _x: string = '', + _y: string = '', } ]] @@ -2137,3 +2131,371 @@ local = 1 << 2 [[ local x: integer = 4 ]] + +TEST [[ +local function test1() + return 1, 2, 3 +end + +local function () + return test1() +end +]] +[[ +function test2() + -> integer + 2. integer + 3. integer +]] + +TEST [[ +---@param x number +---@return boolean +local function ( + x +) +end +]] + +TEST [[ +---@class A +---@field private x number +---@field y number +local +]] +[[ +local t: A { + x: number, + y: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field y number + +---@type A +local +]] +[[ +local t: A { + y: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field y number + = {} +]] +[[ +(global) t: A { + x: number, + y: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field y number + +---@type A + = {} +]] +[[ +(global) t: A { + y: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field y number + +---@type A +v. = {} +]] +[[ +(global) v.t: A { + y: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@type A +local +]] +[[ +local t: A { + z: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@class B: A +local +]] +[[ +local t: B { + y: number, + z: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@class B: A +---@field private a number +local +]] +[[ +local t: B { + a: number, + y: number, + z: number, +} +]] + +TEST [[ +---@class A +---@field private x number +---@field protected y number +---@field z number + +---@class B: A +---@field private a number + +---@type B +local +]] +[[ +local t: B { + z: number, +} +]] + +TEST [[ +---@class A +local mt = {} + +---@private +function mt:init() +end + +---@protected +function mt:update() +end + +function mt:get() +end + +print() +]] +[[ +local mt: A { + get: function, + init: function, + update: function, +} +]] + +TEST [[ +---@class A +local mt = {} + +---@private +function mt:init() +end + +---@protected +function mt:update() +end + +function mt:get() +end + +---@type A +local +]] +[[ +local obj: A { + get: function, +} +]] + +TEST [[ +---@class A +local mt = {} + +---@private +function mt:init() +end + +---@protected +function mt:update() +end + +function mt:get() +end + +---@class B: A +local +]] +[[ +local obj: B { + get: function, + update: function, +} +]] + +TEST [[ +---@class A +local mt = {} + +---@private +function mt:init() +end + +---@protected +function mt:update() +end + +function mt:get() +end + +---@class B: A + +---@type B +local +]] +[[ +local obj: B { + get: function, +} +]] + +TEST [[ +---@class A +local M = {} + +---@private +M.x = 0 + +---@private +function M:init() + self.x = 1 +end + +---@type A +local +]] +[[ +local a: A +]] + +TEST [[ +---@class A +---@field x fun(): string + +---@type table +local obj + +local x = obj[''].() +]] +[[ +(field) A.x: fun():string +]] + +TEST [[ +---@class A +---@field x number + +---@see +]] +[[ +(field) A.x: number +]] + +TEST [[ +---@type { [string]: string }[] +local t + +print(.foo) +]] +[[ +local t: { [string]: string }[] { + foo: unknown, +} +]] + +TEST [[ +local t = { + ['x'] = 1, + ['y'] = 2, +} + +print(t.x, .y) +]] +[[ +local t: { + ['x']: integer = 1, + ['y']: integer = 2, +} +]] + +TEST [[ +local enum = { a = 1, b = 2 } + +local = { + [enum.a] = true, + [enum.b] = 2, + [3] = {} +} +]] +[[ +local t: { + [1]: boolean = true, + [2]: integer = 2, + [3]: table, +} +]] + +TEST [[ +---@class A +---@overload fun(x: number): boolean +local +]] +[[ +local x: A +]] + +TEST [[ +---@type A +local + +---@enum A +local t = { + x = f, +} +]] +[[ +local f: A +]] diff --git a/test/other/filewatch.lua b/test/other/filewatch.lua new file mode 100644 index 000000000..f5d5a03d9 --- /dev/null +++ b/test/other/filewatch.lua @@ -0,0 +1,44 @@ +local thread = require 'bee.thread' +local fw = require 'filewatch' +local fs = require 'bee.filesystem' +local fsu = require 'fs-utility' + +local path = fs.path(LOGPATH) / 'fw' + +fs.create_directories(path) + +os.remove((path / 'test.txt'):string()) + +local _ = fw.watch(path:string(), true) +fsu.saveFile(path / 'test.txt', 'test') + +local events +fw.event(function (ev, filename) + events[#events+1] = {ev, filename} +end) + +thread.sleep(1) +events = {} +fw.update() +assert(#events == 1) +assert(events[1][1] == 'create') + +fsu.saveFile(path / 'test.txt', 'modify') + +thread.sleep(1) +events = {} +fw.update() +assert(#events == 1) +assert(events[1][1] == 'change') + +local f = io.open((path / 'test.txt'):string(), 'w') +assert(f) +f:write('xxx') +f:flush() +f:close() + +thread.sleep(1) +events = {} +fw.update() +assert(#events == 1) +assert(events[1][1] == 'change') diff --git a/test/other/init.lua b/test/other/init.lua index 069a24548..fbe9d923b 100644 --- a/test/other/init.lua +++ b/test/other/init.lua @@ -1,11 +1 @@ -local fs = require 'bee.filesystem' -local platform = require 'bee.platform' -local path = fs.path '/a/b/c/d/e/../../../..' -local absolute = fs.absolute(path) -if platform.OS == 'Windows' then - assert(absolute:string():sub(-2) == '/a', absolute:string()) -elseif platform.OS == 'Linux' then - assert(absolute:string():sub(-3) == '/a/', absolute:string()) -elseif platform.OS == 'macOS' then - -- ไธๆ”ฏๆŒ -end +--require 'other.filewatch' diff --git a/test/plugins/ast/helper.lua b/test/plugins/ast/helper.lua new file mode 100644 index 000000000..f892ea1b3 --- /dev/null +++ b/test/plugins/ast/helper.lua @@ -0,0 +1,64 @@ +local helper = require 'plugins.astHelper' +local parser = require 'parser' + +function Run(script, plugin) + local state = parser.compile(script, "Lua", "Lua 5.4") + plugin(state) + parser.luadoc(state) + return state +end + +local function TestInsertDoc(script) + local state = Run(script, function (state) + local comment = assert(helper.buildComment("class", "AA", state.ast[1].start)) + helper.InsertDoc(state.ast, comment) + end) + assert(state.ast[1].bindDocs) +end + +TestInsertDoc("A={}") + +local function TestaddClassDoc(script) + local state = Run(script, function (state) + assert(helper.addClassDoc(state.ast, state.ast[1], "AA")) + end) + assert(state.ast[1].bindDocs) +end + +TestaddClassDoc [[a={}]] + +TestaddClassDoc [[local a={}]] + +local function TestaddClassDocAtParam(script, index) + index = index or 1 + local arg + local state = Run(script, function (state) + local func = state.ast[1].value + local ok + ok, arg = helper.addClassDocAtParam(state.ast, "AA", func, index) + assert(ok) + end) + assert(arg.bindDocs) +end + +TestaddClassDocAtParam [[ + function a(b) end +]] + +local function TestaddParamTypeDoc(script, index) + index = index or 1 + local func + Run(script, function (state) + func = state.ast[1].value + assert(helper.addParamTypeDoc(state.ast, "string", func.args[index])) + end) + assert(func.args[index].bindDocs) +end + +TestaddParamTypeDoc [[ + local function t(a)end +]] + +TestaddParamTypeDoc([[ + local function t(a,b,c,d)end +]], 4) diff --git a/test/plugins/ast/test.lua b/test/plugins/ast/test.lua new file mode 100644 index 000000000..a01b9e2ee --- /dev/null +++ b/test/plugins/ast/test.lua @@ -0,0 +1,145 @@ +local parser = require 'parser' +local guide = require 'parser.guide' +local helper = require 'plugins.astHelper' + +---@diagnostic disable: await-in-sync +---@param ... function checkers +local function TestPlugin(script, plugin, ...) + local state = parser.compile(script, "Lua", "Lua 5.4") + state.ast = plugin(TESTURI, state.ast) or state.ast + parser.luadoc(state) + for i = 1, select('#', ...) do + select(i, ...)(state) + end +end + +local function isDocClass(ast) + return ast.bindDocs[1].type == 'doc.class' +end + +local function TestAIsClass(state, next) + assert(isDocClass(state.ast[1])) +end + +--- when call Class +local function plugin_AddClass(uri, ast) + guide.eachSourceType(ast, "call", function (source) + local node = source.node + if not guide.isGet(node) then + return + end + if not guide.isGlobal(node) then + return + end + if guide.getKeyName(node) ~= 'Class' then + return + end + local wants = { + ['local'] = true, + ['setglobal'] = true + } + local classnameNode = guide.getParentTypes(source, wants) + if not classnameNode then + return + end + local classname = guide.getKeyName(classnameNode) + if classname then + helper.addClassDoc(ast, classnameNode, classname) + end + end) +end + +local function plugin_AddClassAtParam(uri, ast) + guide.eachSourceType(ast, "function", function (src) + helper.addClassDocAtParam(ast, "A", src, 1) + end) +end + +local function TestSelfIsClass(state, next) + guide.eachSourceType(state.ast, "local", function (source) + if source[1] == 'self' then + assert(source.bindDocs) + assert(source.parent.type == 'function') + assert(#source.parent.args == 0) + end + end) +end + +local function TestAHasField(state, next) + local foundFields = false + local cb = function (source) + if not source.bindDocs or not source.bindDocs[1].class or source.bindDocs[1].class[1] ~= "A" then + return + end + assert(#source.bindDocs == 1) + assert(#source.bindDocs[1].fields >= 2) + assert(source.bindDocs[1].fields[1].field[1] == "x") + assert(source.bindDocs[1].fields[2].field[1] == "y") + foundFields = true + end + guide.eachSourceType(state.ast, "local", cb) + guide.eachSourceType(state.ast, "setglobal", cb) + assert(foundFields) +end + +local function plugin_AddMultipleDocs(uri, ast) + guide.eachSourceType(ast, "call", function(source) + local node = source.node + if guide.getKeyName(node) ~= 'Class' then + return + end + local wants = { + ['local'] = true, + ['setglobal'] = true + } + local classnameNode = guide.getParentTypes(source, wants) + if not classnameNode then + return + end + local classname = guide.getKeyName(classnameNode) + if classname then + local group = {} + helper.addClassDoc(ast, classnameNode, classname, group) + helper.addDoc(ast, classnameNode, "field", "x number", group) + helper.addDoc(ast, classnameNode, "field", "y string", group) + end + end) +end + +local function TestPlugin1(script) + TestPlugin(script, plugin_AddClass, TestAIsClass) +end + +local function TestPlugin2(script) + TestPlugin(script, plugin_AddClassAtParam, TestSelfIsClass) +end + +local function TestPlugin3(script) + TestPlugin(script, plugin_AddMultipleDocs, TestAIsClass, TestAHasField) +end + +TestPlugin1 [[ + local A = Class(function() end) +]] + +TestPlugin1 [[ + A = Class(function() end) +]] + +TestPlugin2 [[ + local function ctor(self) end +]] + +TestPlugin2 [[ + function ctor(self) end +]] + +TestPlugin3 [[ + local A = Class() +]] + +TestPlugin3 [[ + A = Class() +]] + +require 'plugins.ast.helper' diff --git a/test/plugins/ffi/builder.lua b/test/plugins/ffi/builder.lua new file mode 100644 index 000000000..ebbfab551 --- /dev/null +++ b/test/plugins/ffi/builder.lua @@ -0,0 +1,233 @@ +local ffi = require 'plugins.ffi' +local util = require 'utility' +rawset(_G, 'TEST', true) + +local function removeEmpty(lines) + local removeLines = {} + for i, v in ipairs(lines) do + if v ~= '\n' then + removeLines[#removeLines+1] = v:gsub('^%s+', '') + end + end + return removeLines +end + +local function formatLines(lines) + if not lines or #lines == 0 then + return {} + end + table.remove(lines, 1) + return removeEmpty(lines) +end + +---@param str string +local function splitLines(str) + local lines = {} + local i = 1 + for line in str:gmatch("[^\r\n]+") do + lines[i] = line + i = i + 1 + end + return lines +end + +function TEST(wanted) + wanted = removeEmpty(splitLines(wanted)) + return function (script) + local lines = formatLines(ffi.compileCodes({ script })) + assert(util.equal(wanted, lines), util.dump(lines)) + end +end + +TEST [[ + ---@alias ffi.namespace*.EVP_MD ffi.namespace*.struct@env_md_st + + ---@return ffi.namespace*.EVP_MD + function m.EVP_md5() end +]] [[ + typedef struct env_md_st EVP_MD; + const EVP_MD *EVP_md5(void); +]] + +TEST [[ + ---@class ffi.namespace*.struct@a + ---@field _in integer +]] [[ + struct a { + int in; + }; +]] + +TEST [[ +---@param _in integer +function m.test(_in) end +]] [[ + void test(int in); +]] + +TEST [[ + ---@alias ffi.namespace*.ENGINE ffi.namespace*.struct@engine_st + ---@alias ffi.namespace*.ENGINE1 ffi.namespace*.enum@engine_st1 +]] [[ + typedef struct engine_st ENGINE; + typedef enum engine_st1 ENGINE1; +]] + +TEST [[ + ---@param a integer[][] + function m.test(a) end +]] [[ + void test(int a[][]); +]] + +TEST [[ + ---@class ffi.namespace*.struct@A + ---@field b integer[] + ---@field c integer[] +]] [[ + struct A { + int b[5]; + int c[]; + }; +]] + +TEST [[ + m.B = 5 + m.A = 0 + m.D = 7 + m.C = 6 +]] [[ + enum { + A, + B=5, + C, + D, + }; +]] + +TEST [[ + m.B = 2 + m.A = 1 + m.C = 5 + ---@alias ffi.namespace*.enum@a 1 | 2 | 'B' | 'A' | 5 | 'C' +]] [[ + enum a { + A = 1, + B = 2, + C = A|B+2, + }; +]] + +TEST [[ + ---@param a boolean + ---@param b boolean + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(bool a, _Bool b, size_t c, ssize_t d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(int8_t a, int16_t b, int32_t c, int64_t d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(uint8_t a, uint16_t b, uint32_t c, uint64_t d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(unsigned char a, unsigned short b, unsigned long c, unsigned int d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(unsigned char a, unsigned short b, unsigned long c, unsigned int d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(signed char a, signed short b, signed long c, signed int d); +]] + +TEST [[ + ---@param a integer + ---@param b integer + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(char a, short b, long c, int d); +]] + +TEST [[ + ---@param a number + ---@param b number + ---@param c integer + ---@param d integer + function m.test(a, b, c, d) end +]] [[ + void test(float a, double b, int8_t c, uint8_t d); +]] + +TEST [[ + ---@alias ffi.namespace*.H ffi.namespace*.void + + function m.test() end +]] [[ + typedef void H; + + H test(); +]] + +TEST [[ + ---@class ffi.namespace*.a + + ---@param a ffi.namespace*.a + function m.test(a) end +]] [[ + typedef struct {} a; + + void test(a* a); +]] + +TEST [[ + ---@class ffi.namespace*.struct@a + ---@field a integer + ---@field b ffi.namespace*.char* + + ---@param a ffi.namespace*.struct@a + function m.test(a) end +]] [[ + struct a {int a;char* b;}; + + void test(struct a* a); +]] diff --git a/test/plugins/ffi/cdef.lua b/test/plugins/ffi/cdef.lua new file mode 100644 index 000000000..cf3992d35 --- /dev/null +++ b/test/plugins/ffi/cdef.lua @@ -0,0 +1,61 @@ + +local files = require 'files' +local code = require 'plugins.ffi.searchCode' +local cdefRerence = require 'plugins.ffi.cdefRerence' + +rawset(_G, 'TEST', true) + +function TEST(wanted) + ---@async + return function (script) + files.setText(TESTURI, script) + local codeResults = code(cdefRerence(), TESTURI) + assert(codeResults) + table.sort(codeResults) + assert(table.concat(codeResults, '|') == wanted, table.concat(codeResults, '|') .. ' ~= ' .. wanted) + files.remove(TESTURI) + end +end + +TEST 'aaa|bbb' [[ +local ffi = require 'ffi' +local cdef = ffi.cdef +cdef('aaa') +cdef = function () +end +cdef('bbb') +]] + +TEST 'aaa' [[ +local ffi = require 'ffi' + +ffi.cdef('aaa') +]] + +TEST 'aa.aa' [[ +local ffi = require 'ffi' +local t1 = ffi + +t1.cdef"aa.aa" +]] + +TEST 'aaa' [[ +local ffi = require 'ffi' +local code = 'aaa' +ffi.cdef(code) +]] + +TEST 'aaa|bbb' [[ +local ffi = require 'ffi' +local code = 'aaa' +code = 'bbb' +local t1 = ffi +t1.cdef(code) +]] + +TEST 'aa.aa' [[ +local ffi = require 'ffi' +local cdef = ffi.cdef + +cdef"aa.aa" +]] diff --git a/test/plugins/ffi/parser.lua b/test/plugins/ffi/parser.lua new file mode 100644 index 000000000..6d7f2cea5 --- /dev/null +++ b/test/plugins/ffi/parser.lua @@ -0,0 +1,176 @@ +local utility = require 'utility' +local cdriver = require 'plugins.ffi.c-parser.cdriver' + +rawset(_G, 'TEST', true) +local ctypes = require 'plugins.ffi.c-parser.ctypes' +ctypes.TESTMODE = true + +--TODO expand all singlenode +function TEST(wanted, full) + return function (script) + local rrr = cdriver.process_context(script .. "$EOF$") + assert(rrr) + if full then + for i, v in ipairs(rrr) do + assert(utility.equal(v, wanted[i]), utility.dump(v)) + end + else + assert(utility.equal(rrr[1], wanted), utility.dump(rrr[1])) + end + end +end + +TEST { + name = "struct@A", + type = { + fields = { + { + isarray = true, + name = "a", + type = { "int", }, + }, + { + isarray = true, + name = "b", + type = { "int", }, + }, + }, + name = "A", + type = "struct", + }, +} + [[ + struct A { + int a[5]; + int b[]; + }; +]] + +TEST { + name = 'union@a', + type = { + name = 'a', + type = 'union', + fields = { + { name = 'b', type = { 'int' } }, + { name = 'c', type = { 'int8_t' } } + } + } +} [[ + union a{ + int b; + int8_t c; + }; +]] + +TEST { + name = 'union@a', + type = { + name = 'a', + type = 'union', + } +} [[ + union a{}; +]] + +TEST { + name = 'enum@anonymous', + type = { + type = 'enum', + values = { + { name = 'a', value = { '1' } }, + { name = 'b', value = { 'a' } }, + } + } +} [[ + enum { + a = 1, + b = a, + }; +]] + +TEST { + name = 'enum@a', + type = { + name = 'a', + type = 'enum', + values = { + { name = 'b', value = { op = '|', { '1' }, { '2' } } }, + } + } +} [[ + enum a{ + b = 1|2, + }; +]] + +TEST { + name = 'enum@a', + type = { + name = 'a', + type = 'enum', + } +} [[ + enum a{}; +]] + +TEST { + name = 'struct@a', + type = { + name = 'a', + type = 'struct', + fields = { + { name = 'f', type = { 'int' } }, + { name = 'b', type = { 'int', '*', '*' } } + } + } +} [[ + struct a {int f,**b;}; +]] + +TEST { + name = 'struct@a', + type = { + name = 'a', + type = 'struct', + fields = { + { name = 'f', type = { 'int' } }, + } + } +} [[ + struct a {int f;}; +]] + +TEST({ + { name = "struct@anonymous", type = { type = 'struct' } }, + { + name = 'a', + type = { + name = 'a', + type = 'typedef', + def = { + { type = 'struct', } + } + } + } +}, true) [[ + typedef struct {} a; +]] + +TEST { + name = 'a', + type = { + name = 'a', + type = 'function', + params = { + { type = { 'int' }, name = 'b' }, + }, + ret = { + type = { 'int' } + }, + vararg = false + }, + +} [[ + int a(int b); +]] diff --git a/test/plugins/ffi/test.lua b/test/plugins/ffi/test.lua new file mode 100644 index 000000000..93be2ff57 --- /dev/null +++ b/test/plugins/ffi/test.lua @@ -0,0 +1,43 @@ +local lclient = require 'lclient' +local ws = require 'workspace' +local furi = require 'file-uri' +local files = require 'files' +local diagnostic = require 'provider.diagnostic' + +--TODO how to changed the runtime version? +local template = require 'config.template' + +template['Lua.runtime.version'].default = 'LuaJIT' + +TESTURI = furi.encode(TESTROOT .. 'unittest.ffi.lua') + +---@async +local function TestBuilder() + local builder = require 'core.command.reloadFFIMeta' + files.setText(TESTURI, [[ + local ffi = require 'ffi' + ffi.cdef 'void test();' + ]]) + local uri = ws.getFirstScope().uri + builder(uri) +end + +---@async +lclient():start(function (languageClient) + languageClient:registerFakers() + local rootUri = TESTURI + languageClient:initialize { + rootUri = rootUri, + } + + diagnostic.pause() + + ws.awaitReady(rootUri) + + require 'plugins.ffi.cdef' + require 'plugins.ffi.parser' + require 'plugins.ffi.builder' + TestBuilder() + + diagnostic.resume() +end) diff --git a/test/plugins/node/test.lua b/test/plugins/node/test.lua new file mode 100644 index 000000000..15e4d16c3 --- /dev/null +++ b/test/plugins/node/test.lua @@ -0,0 +1,56 @@ +local files = require 'files' +local scope = require 'workspace.scope' +local nodeHelper = require 'plugins.nodeHelper' +local vm = require 'vm' +local guide = require 'parser.guide' + + +local pattern, msg = nodeHelper.createFieldPattern("*.components") +assert(pattern, msg) + +---@param source parser.object +function OnCompileFunctionParam(next, func, source) + if next(func, source) then + return true + end + --ไปŽ่ฏฅๅ‚ๆ•ฐ็š„ไฝฟ็”จๆจกๅผๆฅๆŽจๅฏผ่ฏฅ็ฑปๅž‹ + if nodeHelper.matchPattern(source, pattern) then + local type = vm.declareGlobal('type', 'TestClass', TESTURI) + vm.setNode(source, vm.createNode(type, source)) + return true + end +end + +local myplugin = { OnCompileFunctionParam = OnCompileFunctionParam } + +---@diagnostic disable: await-in-sync +local function TestPlugin(script) + local prefix = [[ + ---@class TestClass + ---@field b string + ]] + ---@param checker fun(state:parser.state) + return function (plugin, checker) + files.open(TESTURI) + files.setText(TESTURI, prefix .. script, true) + scope.getScope(TESTURI):set('pluginInterfaces', plugin) + local state = files.getState(TESTURI) + assert(state) + checker(state) + files.remove(TESTURI) + end +end + +TestPlugin [[ + local function t(a) + a.components:test() + end +]](myplugin, function (state) + guide.eachSourceType(state.ast, 'local', function (src) + if guide.getKeyName(src) == 'a' then + local node = vm.compileNode(src) + assert(node) + assert(not vm.isUnknown(node)) + end + end) +end) diff --git a/test/plugins/test.lua b/test/plugins/test.lua new file mode 100644 index 000000000..b6533de9d --- /dev/null +++ b/test/plugins/test.lua @@ -0,0 +1,3 @@ +require 'plugins.ast.test' +require 'plugins.ffi.test' +require 'plugins.node.test' diff --git a/test/references/all.lua b/test/references/all.lua index 9395df861..3c376c218 100644 --- a/test/references/all.lua +++ b/test/references/all.lua @@ -126,3 +126,16 @@ end local v1 = Master:foobar("", Dog) v1.() ]] + +TEST [[ +---@class A +A = {} + +function A:<~TestA~>() +end + +---@param param A +function TestB(param) + param:() +end +]] diff --git a/test/references/common.lua b/test/references/common.lua index c20b32edf..9cf128a7a 100644 --- a/test/references/common.lua +++ b/test/references/common.lua @@ -201,3 +201,48 @@ local b = { } a.color = { 1, 1, 1 } b.<~color~> = a.color ]] + +TEST [[ +---@alias <~A~> number + +---@type +]] + +TEST [[ +---@class A +---@field <~x~> number + +---@type A +local t +print(t.) +]] + +TEST [[ +---@class A +---@field number + +---@type A +local t1 + +t1.<~x~> = 1 + +---@type A +local t2 + +t2. = 1 +]] + +TEST [[ +---@alias lang 'en' | 'de' + +---@class A +local a + +---@type lang +a.test = 'en' + +---@class B +local b + +b.?> = a.test +]] diff --git a/test/references/init.lua b/test/references/init.lua index 1b1cc73b7..3ecc46bd8 100644 --- a/test/references/init.lua +++ b/test/references/init.lua @@ -21,11 +21,11 @@ end function TEST(script) local newScript, catched = catch(script, '!?~') - files.setText('', newScript) + files.setText(TESTURI, newScript) local input = catched['?'] + catched['~'] local expect = catched['!'] + catched['~'] - local results = core('', input[1][1]) + local results = core(TESTURI, input[1][1], true) if results then local positions = {} for i, result in ipairs(results) do @@ -35,7 +35,7 @@ function TEST(script) else assert(#expect == 0) end - files.remove('') + files.remove(TESTURI) end require 'references.common' diff --git a/test/rename/init.lua b/test/rename/init.lua index 64e3916af..dcb5f9e50 100644 --- a/test/rename/init.lua +++ b/test/rename/init.lua @@ -6,7 +6,7 @@ local guide = require 'parser.guide' local config = require 'config' local function replace(text, positions) - local state = files.getState('') + local state = files.getState(TESTURI) local buf = {} table.sort(positions, function (a, b) return a.start < b.start @@ -26,19 +26,19 @@ end function TEST(oldName, newName) return function (oldScript) return function (expectScript) - files.setText('', oldScript) - local state = files.getState('') + files.setText(TESTURI, oldScript) + local state = files.getState(TESTURI) local offset = oldScript:find('[^%w_]'..oldName..'[^%w_]') assert(offset) local position = guide.offsetToPosition(state, offset) - local positions = core.rename('', position, newName) + local positions = core.rename(TESTURI, position, newName) local script = oldScript if positions then script = replace(script, positions) end assert(script == expectScript) - files.remove('') + files.remove(TESTURI) end end end @@ -212,3 +212,37 @@ function f(arg2) print(arg2) end ]] + +TEST ('field1', 'field2') [[ +---@class A +---@field field1 number + +---@type A +local t + +print(t.field1) +]] [[ +---@class A +---@field field2 number + +---@type A +local t + +print(t.field2) +]] + +TEST ('A', 'C') [[ +---@class A + +---@class B: A +]] [[ +---@class C + +---@class B: C +]] + +TEST ('A', 'C') [[ +---@class B: A +]] [[ +---@class B: C +]] diff --git a/test/signature/init.lua b/test/signature/init.lua index 9c05f7c55..bd4d40cd4 100644 --- a/test/signature/init.lua +++ b/test/signature/init.lua @@ -8,8 +8,8 @@ rawset(_G, 'TEST', true) function TEST(script) return function (expect) local newScript, catched1 = catch(script, '?') - files.setText('', newScript) - local hovers = core('', catched1['?'][1][1]) + files.setText(TESTURI, newScript) + local hovers = core(TESTURI, catched1['?'][1][1]) if hovers then assert(#hovers == #expect) for i, hover in ipairs(hovers) do @@ -28,7 +28,7 @@ function TEST(script) else assert(expect == nil) end - files.remove('') + files.remove(TESTURI) end end @@ -162,7 +162,7 @@ TEST [[ for _ in pairs() do end ]] -{'function pairs(!>)'} +{'function pairs(!>)'} TEST [[ function m:f() @@ -235,11 +235,8 @@ end)() {'function (, b: any)'} TEST [[ -function X() end - ----@param a number -function X(a) end - +---@overload fun() +---@overload fun(a:number) ---@param a number ---@param b number function X(a, b) end @@ -252,12 +249,9 @@ X() 'function X(, b: number)', } -TEST [[ -function X() end - ----@param a number -function X(a) end - +TEST [[\ +---@overload fun() +---@overload fun(a:number) ---@param a number ---@param b number function X(a, b) end @@ -270,11 +264,8 @@ X() } TEST [[ -function X() end - ----@param a number -function X(a) end - +---@overload fun() +---@overload fun(a:number) ---@param a number ---@param b number function X(a, b) end @@ -286,11 +277,8 @@ X(1, ) } TEST [[ -function X() end - ----@param a number -function X(a) end - +---@overload fun() +---@overload fun(a:number) ---@param a number ---@param b number function X(a, b) end @@ -300,3 +288,114 @@ X(1, ) { 'function X(a: number, )', } + +TEST [[ +---@alias A { x:number, y:number, z:number } + +---comment +---@param a A +---@param b string +function X(a, b) + +end + +X({}, ) +]] +{ +'function X(a: { x: number, y: number, z: number }, )' +} + +TEST [[ +---@overload fun(x: number) +---@overload fun(x: number, y: number) +local function f(...) +end + +f() +]] +{ +'function f()', +'function f(, y: number)', +} + +TEST [[ +---@class A +---@overload fun(x: number) + +---@type A +local t + +t() +]] +{ +'function ()', +} + +TEST [[ +---@class ๐Ÿ˜… + +---@param a ๐Ÿ˜… +---@param b integer +local function f(a, b) +end + +f(1, 2) +]] +{ +'function f(a: ๐Ÿ˜…, )', +} + +TEST [[ +---@class A +---@field event fun(self: self, ev: "onChat", c: string) +---@field event fun(self: self, ev: "onTimer", t: integer) + +---@type A +local t + +t:event("onChat", ) +]] +{ +'(method) (ev: "onChat", )', +} + +TEST [[ +---@class A +---@field event fun(self: self, ev: "onChat", c: string) +---@field event fun(self: self, ev: "onTimer", t: integer) + +---@type A +local t + +t:event("onTimer", ) +]] +{ +'(method) (ev: "onTimer", )', +} + +local config = require 'config' +config.set(nil, "Lua.type.inferParamType", true) + +TEST [[ +local function x(a, b) +end + +x("1", ) +]] +{ +'function x(a: string, )' +} + +TEST [[ +local function x(a) + +end +x('str') +x(1) +x() +]] +{ +'function x()', +} + +config.set(nil, "Lua.type.inferParamType", false) diff --git a/test/tclient/init.lua b/test/tclient/init.lua index 6f8611428..f945d570e 100644 --- a/test/tclient/init.lua +++ b/test/tclient/init.lua @@ -5,10 +5,14 @@ require 'tclient.tests.folders-with-single-file' require 'tclient.tests.load-library' require 'tclient.tests.files-associations' require 'tclient.tests.resolve-completion' -require 'tclient.tests.performance-jass-common' require 'tclient.tests.hover-pairs' require 'tclient.tests.change-workspace-folder' require 'tclient.tests.jump-source' require 'tclient.tests.load-relative-library' require 'tclient.tests.hover-set-local' +require 'tclient.tests.same-prefix' +require 'tclient.tests.recursive-runner' +require 'tclient.tests.modify-luarc' + +require 'tclient.tests.performance-jass-common' require 'tclient.tests.build-meta' diff --git a/test/tclient/tests/change-workspace-folder.lua b/test/tclient/tests/change-workspace-folder.lua index abd385cb1..a9b0e36a8 100644 --- a/test/tclient/tests/change-workspace-folder.lua +++ b/test/tclient/tests/change-workspace-folder.lua @@ -98,4 +98,22 @@ lclient():start(function (client) assert(files.getState(rootUri .. '/ws1/test.lua') == nil) assert(files.getState(rootUri .. '/ws2/test.lua') == nil) assert(files.getState(rootUri .. '/ws3/test.lua') ~= nil) + + -- normalize uri + client:notify('workspace/didChangeWorkspaceFolders', { + event = { + added = { + { + name = 'ws2', + uri = rootUri .. '%2F%77%73%32'--[[/ws2]], + }, + }, + removed = {}, + }, + }) + + ws.awaitReady(rootUri .. '/ws2') + assert(files.getState(rootUri .. '/ws1/test.lua') == nil) + assert(files.getState(rootUri .. '/ws2/test.lua') ~= nil) + assert(files.getState(rootUri .. '/ws3/test.lua') ~= nil) end) diff --git a/test/tclient/tests/jump-source.lua b/test/tclient/tests/jump-source.lua index 84a4dcd5c..c9e093c03 100644 --- a/test/tclient/tests/jump-source.lua +++ b/test/tclient/tests/jump-source.lua @@ -163,7 +163,7 @@ print(D3) position = { line = 9, character = 7 }, }) - if platform.OS == 'Windows' then + if platform.os == 'windows' then assert(util.equal(locations, { { uri = 'file:///d%3A/xxx/2.lua', diff --git a/test/tclient/tests/modify-luarc.lua b/test/tclient/tests/modify-luarc.lua new file mode 100644 index 000000000..62d97a417 --- /dev/null +++ b/test/tclient/tests/modify-luarc.lua @@ -0,0 +1,377 @@ +local lclient = require 'lclient' +local util = require 'utility' +local ws = require 'workspace' +local jsonc = require 'jsonc' +local jsonb = require 'json-beautify' +local client = require 'client' +local provider = require 'provider' +local json = require 'json' +local config = require 'config' + +local configPath = LOGPATH .. '/modify-luarc.json' + +---@async +lclient():start(function (languageClient) + languageClient:registerFakers() + + CONFIGPATH = configPath + + languageClient:initialize() + + ws.awaitReady() + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify(json.createEmptyObject())) + + provider.updateConfig() + + client.setConfig({ + { + action = 'set', + key = 'Lua.runtime.version', + value = 'LuaJIT', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['runtime.version'] = 'LuaJIT', + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['Lua.runtime.version'] = json.null, + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'set', + key = 'Lua.runtime.version', + value = 'LuaJIT', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['Lua.runtime.version'] = 'LuaJIT', + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify(json.createEmptyObject())) + + provider.updateConfig() + + client.setConfig({ + { + action = 'add', + key = 'Lua.diagnostics.disable', + value = 'undefined-global', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['diagnostics.disable'] = { + 'undefined-global', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['Lua.diagnostics.disable'] = {} + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'add', + key = 'Lua.diagnostics.disable', + value = 'undefined-global', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['Lua.diagnostics.disable'] = { + 'undefined-global', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['Lua.diagnostics.disable'] = { + 'unused-local' + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'add', + key = 'Lua.diagnostics.disable', + value = 'undefined-global', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['Lua.diagnostics.disable'] = { + 'unused-local', + 'undefined-global', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify(json.createEmptyObject())) + + provider.updateConfig() + + client.setConfig({ + { + action = 'prop', + key = 'Lua.runtime.special', + prop = 'include', + value = 'require', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['runtime.special'] = { + ['include'] = 'require', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['Lua.runtime.special'] = json.createEmptyObject() + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'prop', + key = 'Lua.runtime.special', + prop = 'include', + value = 'require', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['Lua.runtime.special'] = { + ['include'] = 'require', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['Lua.runtime.special'] = { + ['import'] = 'require', + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'prop', + key = 'Lua.runtime.special', + prop = 'include', + value = 'require', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['Lua.runtime.special'] = { + ['import'] = 'require', + ['include'] = 'require', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + ['runtime.version'] = json.null, + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'set', + key = 'Lua.runtime.version', + value = 'LuaJIT', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['runtime.version'] = 'LuaJIT', + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + Lua = { + runtime = { + version = json.null, + } + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'set', + key = 'Lua.runtime.version', + value = 'LuaJIT', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + Lua = { + runtime = { + version = 'LuaJIT', + } + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + runtime = { + version = json.null, + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'set', + key = 'Lua.runtime.version', + value = 'LuaJIT', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + runtime = { + version = 'LuaJIT', + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + diagnostics = { + disable = { + 'unused-local', + } + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'add', + key = 'Lua.diagnostics.disable', + value = 'undefined-global', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + diagnostics = { + disable = { + 'unused-local', + 'undefined-global', + } + } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify { + runtime = { + special = { + import = 'require', + } + } + }) + + provider.updateConfig() + + client.setConfig({ + { + action = 'prop', + key = 'Lua.runtime.special', + prop = 'include', + value = 'require', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + runtime = { + special = { + import = 'require', + include = 'require', + } + } + })) + + ------------------------------- + -- merrge other configs -- + ------------------------------- + + util.saveFile(configPath, jsonb.beautify(json.createEmptyObject())) + + provider.updateConfig() + + config.add(nil, 'Lua.diagnostics.globals', 'x') + config.add(nil, 'Lua.diagnostics.globals', 'y') + + client.setConfig({ + { + action = 'add', + key = 'Lua.diagnostics.globals', + value = 'z', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['diagnostics.globals'] = { 'x', 'y', 'z' } + })) + + ------------------------------- + + util.saveFile(configPath, jsonb.beautify(json.createEmptyObject())) + + provider.updateConfig() + + config.prop(nil, 'Lua.runtime.special', 'kx', 'require') + config.prop(nil, 'Lua.runtime.special', 'ky', 'require') + + client.setConfig({ + { + action = 'prop', + key = 'Lua.runtime.special', + prop = 'kz', + value = 'require', + } + }) + + assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), { + ['runtime.special'] = { + ['kx'] = 'require', + ['ky'] = 'require', + ['kz'] = 'require', + } + })) +end) diff --git a/test/tclient/tests/recursive-runner.lua b/test/tclient/tests/recursive-runner.lua new file mode 100644 index 000000000..e824f23a2 --- /dev/null +++ b/test/tclient/tests/recursive-runner.lua @@ -0,0 +1,180 @@ +local lclient = require 'lclient' +local ws = require 'workspace' +local await = require 'await' +local config = require 'config' +local vm = require 'vm' +local guide = require 'parser.guide' +local files = require 'files' + +---@async +lclient():start(function (client) + client:registerFakers() + client:initialize() + + config.set(nil, 'Lua.diagnostics.enable', false) + + ws.awaitReady() + + client:notify('textDocument/didOpen', { + textDocument = { + uri = 'file:///test.lua', + languageId = 'lua', + version = 0, + text = [[ +---@type number +local x + +---@type number +local y + +x = y + +y = x +]] + } + }) + + await.sleep(0.1) + + local hover1 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 1, character = 7 }, + }) + + local hover2 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 8, character = 1 }, + }) + + assert(hover1.contents.value:find 'number') + assert(hover2.contents.value:find 'number') + + client:notify('textDocument/didOpen', { + textDocument = { + uri = 'file:///test.lua', + languageId = 'lua', + version = 1, + text = [[ +---@type number +local x + +---@type number +local y + +---@type number +local z + +x = y +y = z +z = y +x = y +x = z +z = x +y = x +]] + } + }) + + + await.sleep(0.1) + + local hover1 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 9, character = 0 }, + }) + + local hover2 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 10, character = 0 }, + }) + + local hover3 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 11, character = 0 }, + }) + + local hover4 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 12, character = 0 }, + }) + + local hover5 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 13, character = 0 }, + }) + + local hover6 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 14, character = 0 }, + }) + + local hover7 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 15, character = 0 }, + }) + + assert(hover1.contents.value:find 'number') + assert(hover2.contents.value:find 'number') + assert(hover3.contents.value:find 'number') + assert(hover4.contents.value:find 'number') + assert(hover5.contents.value:find 'number') + assert(hover6.contents.value:find 'number') + assert(hover7.contents.value:find 'number') + + client:notify('textDocument/didOpen', { + textDocument = { + uri = 'file:///test.lua', + languageId = 'lua', + version = 2, + text = [[ +---@meta + +---@class vector3 +---@operator add(vector3): vector3 +---@operator sub(vector3): vector3 +---@operator mul(vector3): number +---@operator mul(number): vector3 +---@operator div(number): vector3 +local mt + +---@return vector3 +function mt:normalize() end + +---@param target vector3 +function Walk(target) + local moveSpeed = 1.0 + local deltalTime = 2.0 + + ---@type vector3 + local curPos + local targetDirVec = (target - curPos):normalize() + local stepMove = targetDirVec * (moveSpeed * deltalTime) + local nextPos = curPos + stepMove + + curPos = nextPos +end +]] + } + }) + + local hover1 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 20, character = 11 }, + }) + assert(hover1.contents.value:find 'vector3') + + vm.clearNodeCache() + local state = files.getState('file:///test.lua') + assert(state) + guide.eachSourceType(state.ast, 'call', function (src) + vm.compileNode(src) + end) + + local hover1 = client:awaitRequest('textDocument/hover', { + textDocument = { uri = 'file:///test.lua' }, + position = { line = 20, character = 11 }, + }) + assert(hover1.contents.value:find 'vector3') + + config.set(nil, 'Lua.diagnostics.enable', true) +end) diff --git a/test/tclient/tests/same-prefix.lua b/test/tclient/tests/same-prefix.lua new file mode 100644 index 000000000..bc2a1f9b6 --- /dev/null +++ b/test/tclient/tests/same-prefix.lua @@ -0,0 +1,57 @@ +local lclient = require 'lclient' +local fs = require 'bee.filesystem' +local util = require 'utility' +local furi = require 'file-uri' +local ws = require 'workspace' +local files = require 'files' +local scope = require 'workspace.scope' + +local rootPath = LOGPATH .. '/same-prefix' +local rootUri = furi.encode(rootPath) + +for _, name in ipairs { 'ws', 'ws1' } do + fs.create_directories(fs.path(rootPath .. '/' .. name)) +end + +---@async +lclient():start(function (client) + client:registerFakers() + + client:initialize { + rootPath = rootPath, + rootUri = rootUri, + workspaceFolders = { + { + name = 'ws', + uri = rootUri .. '/ws', + }, + { + name = 'ws1', + uri = rootUri .. '/ws1', + }, + } + } + + ws.awaitReady(rootUri .. '/ws') + ws.awaitReady(rootUri .. '/ws1') + + files.setText(rootUri .. '/ws1/test.lua', [[ + ]]) + + files.setText(rootUri .. '/ws/main.lua', [[ +require '' + ]]) + + local comps1 = client:awaitRequest('textDocument/completion', { + textDocument = { + uri = rootUri .. '/ws/main.lua', + }, + position = { + line = 0, + character = 9, + }, + }) + for _, item in ipairs(comps1.items) do + assert(item.label ~= 'test') + end +end) diff --git a/test/type_formatting/init.lua b/test/type_formatting/init.lua index 363303cc4..4e9ce5569 100644 --- a/test/type_formatting/init.lua +++ b/test/type_formatting/init.lua @@ -6,17 +6,17 @@ local catch = require 'catch' rawset(_G, 'TEST', true) function TEST(script) - return function (expect) + return function(expect) local newScript, catched = catch(script, '?') - files.setText('', newScript) - local edits = core('', catched['?'][1][1], expect.ch) + files.setText(TESTURI, newScript) + local edits = core(TESTURI, catched['?'][1][1], expect.ch) if edits then assert(expect.edits) assert(util.equal(edits, expect.edits)) else assert(expect.edits == nil) end - files.remove('') + files.remove(TESTURI) end end @@ -117,11 +117,93 @@ TEST [[ } TEST [[ -if true then +local x = 1 + +]] +{ + ch = '\n', + edits = nil, +} + +TEST [[ +local x = 'if 1 then' + +]] +{ + ch = '\n', + edits = { + { + start = 10000, + finish = 10004, + text = '', + } + } +} + +TEST [[ +local x = 'do' + +]] +{ + ch = '\n', + edits = { + { + start = 10000, + finish = 10004, + text = '', + } + } +} + +TEST [[ +local x = 'function' + +]] +{ + ch = '\n', + edits = { + { + start = 10000, + finish = 10004, + text = '', + } + } +} + +TEST [[ +do + +]] +{ + ch = '\n', + edits = nil +} + +TEST [[ +do + +end +]] +{ + ch = '\n', + edits = nil +} + +TEST [[ +function () + +]] +{ + ch = '\n', + edits = nil +} + +TEST [[ +function () end ]] { ch = '\n', - edits = {} + edits = nil } diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index 5f71dfa2d..98a07ca3f 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -7,7 +7,7 @@ local vm = require 'vm' rawset(_G, 'TEST', true) local function getSource(pos) - local state = files.getState('') + local state = files.getState(TESTURI) if not state then return end @@ -21,7 +21,8 @@ local function getSource(pos) or source.type == 'field' or source.type == 'method' or source.type == 'function' - or source.type == 'table' then + or source.type == 'table' + or source.type == 'doc.type.name' then result = source end end) @@ -31,18 +32,22 @@ end function TEST(wanted) return function (script) local newScript, catched = catch(script, '?') - files.setText('', newScript) + files.setText(TESTURI, newScript) local source = getSource(catched['?'][1][1]) assert(source) - local result = vm.getInfer(source):view('') + local result = vm.getInfer(source):view(TESTURI) if wanted ~= result then - vm.getInfer(source):view('') + vm.getInfer(source):view(TESTURI) end assert(wanted == result) - files.remove('') + files.remove(TESTURI) end end +TEST 'nil' [[ +local = nil +]] + TEST 'string' [[ local = '111' ]] @@ -177,7 +182,7 @@ TEST 'boolean' [[ = a == b ]] -TEST 'integer' [[ +TEST 'unknown' [[ = a << b ]] @@ -185,7 +190,7 @@ TEST 'integer' [[ = 1 << 2 ]] -TEST 'string' [[ +TEST 'unknown' [[ = a .. b ]] @@ -201,7 +206,7 @@ TEST 'string' [[ = 'a' .. 1.0 ]] -TEST 'number' [[ +TEST 'unknown' [[ = a + b ]] @@ -226,11 +231,11 @@ local a = - a ]] -TEST 'integer' [[ +TEST 'unknown' [[ = 1 + X ]] -TEST 'number' [[ +TEST 'unknown' [[ = 1.0 + X ]] @@ -1589,25 +1594,7 @@ AAA = {} local = AAA() ]] -TEST 'AA' [[ ----@overload fun():AA -AAA.BBB = {} - - -local = AAA.BBB() -]] - -TEST 'AA' [[ -local AAA - ----@overload fun():AA -AAA.BBB = {} - - -local = AAA.BBB() -]] - -TEST 'string|integer' [[ +TEST 'string' [[ local x = '1' x = 1 @@ -1654,7 +1641,7 @@ function A() end ]] -TEST 'unknown' [[ +TEST 'string' [[ local x function A() @@ -1775,6 +1762,26 @@ x = '1' x = 1 ]] +TEST 'integer' [[ +local x +x = true +do + x = 1 +end +print() +]] + +TEST 'boolean' [[ +local x +x = true +function XX() + do + x = 1 + end +end +print() +]] + TEST 'integer?' [[ ---@type integer? local @@ -1826,6 +1833,17 @@ end print() ]] +TEST 'nil' [[ +---@type integer? +local x + +if not x then + print() +end + +print(x) +]] + TEST 'integer' [[ ---@type integer? local x @@ -1857,6 +1875,15 @@ if xxx and x then end ]] +TEST 'unknown' [[ +---@type integer? +local x + +if not x and x then + print() +end +]] + TEST 'integer' [[ ---@type integer? local x @@ -2294,7 +2321,7 @@ local x print() ]] -TEST 'unknown?' [[ +TEST 'nil' [[ ---@type string? local x @@ -2368,7 +2395,7 @@ end print() ]] -TEST 'unknown?' [[ +TEST 'nil' [[ ---@type integer? local t @@ -2393,7 +2420,7 @@ local = f() TEST 'integer|table' [[ local function returnI() - return a + 1 + return 1 end local function f() @@ -2529,21 +2556,21 @@ end print() ]] -TEST 'table' [[ +TEST 'table' [[ ---@alias xxx table ---@type xxx local ]] -TEST 'unknown[][]' [[ +TEST 'xxx[][]' [[ ---@alias xxx xxx[] ---@type xxx local ]] -TEST 'fun(x: fun(x: unknown))' [[ +TEST 'fun(x: fun(x: xxx))' [[ ---@alias xxx fun(x: xxx) ---@type xxx @@ -2597,6 +2624,17 @@ end print() ]] +TEST 'integer' [[ +---@type integer? +local n + +if not n then + os.exit() +end + +print() +]] + TEST 'table' [[ ---@type table? local n @@ -2870,10 +2908,7 @@ local = echo(b) ]] TEST 'boolean' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -2883,10 +2918,7 @@ local = f() ]] TEST 'number' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -2896,10 +2928,7 @@ local = f(1) ]] TEST 'boolean' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -2913,10 +2942,7 @@ local = f(r0()) ]] TEST 'number' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -2930,10 +2956,7 @@ local = f(r1()) ]] TEST 'boolean' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -2946,10 +2969,7 @@ local = f(r0()) ]] TEST 'number' [[ ----@return boolean -function f() -end - +---@overload fun():boolean ---@param x integer ---@return number function f(x) @@ -3177,7 +3197,7 @@ local function f() end local x, y, = 1, 2, f() ]] -TEST 'function' [[ +TEST 'unknown' [[ local f print() @@ -3185,6 +3205,26 @@ print() function f() end ]] +TEST 'unknown' [[ +local f + +do + print() +end + +function f() end +]] + +TEST 'function' [[ +local f + +function A() + print() +end + +function f() end +]] + TEST 'number' [[ ---@type number|nil local n @@ -3761,3 +3801,538 @@ TEST 'integerA' [[ for = 1, 10 do end ]] + +TEST 'string' [[ +---@class A +---@field x string + +---@class B : A +local t = {} + +t.x = t.x + +print(t.) +]] + +TEST 'unknown' [[ +local t = { + x = 1, +} + +local x + +local = t[x] +]] + +TEST 'A|B' [[ +---@class A +---@class B: A + +---@type A|B +local +]] + +TEST 'function' [[ +---@class myClass +local myClass = { has = { nested = {} } } + +function myClass.has.nested.fn() end + +---@type myClass +local class + +class.has.nested.() +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun(x: T) +---@return T[] +local function x(f) end + +---@param x integer +local = x(function (x) end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +local = x(function () + return 1 +end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@return integer +local = x(function () end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun(x: T) +---@return T[] +local function x(f) end + +---@type fun(x: integer) +local cb + +local = x(cb) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@type fun(): integer +local cb + +local = x(cb) +]] + +TEST 'integer' [[ +---@return fun(x: integer) +local function f() + return function () + end +end +]] + +TEST 'string' [[ +---@class A +---@field f fun(x: string) + +---@type A +local t = { + f = function () end +} +]] + +config.set(nil, 'Lua.runtime.special', { + ['xx.assert'] = 'assert' +}) + +TEST 'number' [[ +---@type number? +local t + +xx.assert(t) + +print() +]] + +config.set(nil, 'Lua.runtime.special', nil) + +TEST 'A' [[ +---@class A +local mt + +---@return +function mt:init() +end +]] + +TEST 'A' [[ +---@class A +local mt + +---@return self +function mt:init() +end + +local = mt:init() +]] + +TEST 'A' [[ +---@class A +---@field x +]] + +TEST 'A' [[ +---@class A +---@field x self + +---@type A +local o + +print(o.) +]] + +TEST 'A' [[ +---@class A +---@overload fun(): self +local A + +local = A() +]] + +TEST 'number' [[ +---@type table<'Test1', fun(x: number)> +local t = { + ["Test1"] = function() end, +} +]] + +TEST 'number' [[ +---@type table<5, fun(x: number)> +local t = { + [5] = function() end, +} +]] + +TEST 'number' [[ +---@type fun(x: number) +local function f() end +]] + +TEST 'boolean' [[ +---@generic T: string | boolean | table +---@param x T +---@return T +local function f(x) + return x +end + +local = f(true) +]] + +TEST 'number' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local = t[1] +]] + +TEST 'boolean' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local = t[2] +]] + +TEST 'N' [[ +---@class N: number +local x + +if x == 0.1 then + print() +end +]] + +TEST 'vec3' [[ +---@class mat4 +---@operator mul(vec3): vec3 -- matrix * vector +---@operator mul(number): mat4 -- matrix * constant + +---@class vec3: number + +---@type mat4, vec3 +local m, v + +local = m * v +]] + +TEST 'mat4' [[ +---@class mat4 +---@operator mul(number): mat4 -- matrix * constant +---@operator mul(vec3): vec3 -- matrix * vector + +---@class vec3: number + +---@type mat4, vec3 +local m, v + +local = m * v +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +else + print() +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +elseif then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif then + ---@cast t A + print(t) +end +]] + +TEST 'function' [[ +local function x() + print() +end +]] + +TEST 'number' [[ +---@type number? +local x + +do + if not x then + return + end +end + +print() +]] + +TEST 'number' [[ +---@type number[] +local xs + +---@type fun(x): number? +local f + +for _, in ipairs(xs) do + x = f(x) +end +]] + +TEST 'number' [[ +---@type number? +X = Y + +if X then + print() +end +]] + +TEST 'number' [[ +---@type number|boolean +X = Y + +if type(X) == 'number' then + print() +end +]] + +TEST 'boolean' [[ +---@type number|boolean +X = Y + +if type(X) ~= 'number' then + print() +end +]] + +TEST 'boolean' [[ +---@type number +X = Y + +---@cast X boolean + +print() +]] + +TEST 'number' [[ +---@type number +local t + +if xxx == then + print(t) +end +]] + +TEST 'V' [[ +---@class V +X = 1 + +print() +]] + +TEST 'V' [[ +---@class V +X.Y = 1 + +print(X.) +]] + +TEST 'integer' [[ +local x = {} + +x.y = 1 +local y = x.y +x.y = nil + +print() +]] + +TEST 'function' [[ +function X() + () +end + +function Y() +end +]] + +TEST 'A_Class' [[ +---@class A_Class +local A = { x = 5 } + +function A:func() + for i = 1, .x do + print(i) + end + + self.y = 3 + self.y = self.y + 3 +end +]] + +TEST 'number' [[ +---@type number? +local n +local = n or error('') +]] + +TEST 'Foo' [[ +---@class Foo +---@operator mul(Foo): Foo +---@operator mul(Bar): Foo +---@class Bar + +---@type Foo +local foo + +---@type Foo|Bar +local fooOrBar + +local = foo * fooOrBar +]] + +TEST 'number' [[ +local a = 4; +local b = 2; + +local = a / b; +]] + +TEST 'string' [[ +local a = '4'; +local b = '2'; + +local = a .. b; +]] + +TEST 'number|{ [1]: string }' [[ +---@alias Some +---| { [1]: string } +---| number + +local x ---@type Some + +print() +]] + +TEST 'integer' [[ +---@class metatable : table +---@field __index table + +---@param table table +---@param metatable? metatable +---@return table +function setmetatable(table, metatable) end + +local m = setmetatable({},{ __index = { a = 1 } }) + +m. +]] + +TEST 'integer' [[ +---@class metatable : table +---@field __index table + +---@param table table +---@param metatable? metatable +---@return table +function setmetatable(table, metatable) end + +local mt = {a = 1 } +local m = setmetatable({},{ __index = mt }) + +m. +]] + +TEST 'integer' [[ +local x = 1 +repeat +until +]] + +-- #2144 +TEST 'A' [=[ +local function f() + return {} --[[@as A]] +end + +local = f() +]=] + +TEST 'A' [=[ +local function f() + ---@type A + return {} +end + +local = f() +]=] +-- diff --git a/theme-tokens.md b/theme-tokens.md index 779811f59..44ed3720f 100644 --- a/theme-tokens.md +++ b/theme-tokens.md @@ -1,46 +1,46 @@ ## Syntax Tokens -Provide by custom [tmLanguage file](https://github.com/sumneko/vscode-lua/blob/master/syntaxes/lua.tmLanguage.json) +Provide by custom [tmLanguage file](https://github.com/LuaLS/lua.tmbundle/blob/master/lua.tmLanguage.json), which is included by default in Visual Studio Code. Preview in `Dark+` of VSCode | token | preview | | :---- | :---- | -| `keyword.local.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/keyword.local.lua.jpg?raw=true) | -| `keyword.control.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/keyword.control.lua.jpg?raw=true) | -| `entity.name.class.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/entity.name.class.lua.jpg?raw=true) | -| `entity.name.function.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/entity.name.function.lua.jpg?raw=true) | -| `punctuation.definition.parameters.begin.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.parameters.begin.lua.jpg?raw=true) | -| `punctuation.definition.parameters.finish.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.parameters.finish.lua.jpg?raw=true) | -| `variable.parameter.function.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/variable.parameter.function.lua.jpg?raw=true) | -| `punctuation.separator.arguments.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.separator.arguments.lua.jpg?raw=true) | -| `constant.numeric.integer.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.numeric.integer.lua.jpg?raw=true) | -| `constant.numeric.float.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.numeric.float.lua.jpg?raw=true) | -| `constant.numeric.integer.hexadecimal.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.numeric.integer.hexadecimal.lua.jpg?raw=true) | -| `constant.numeric.float.hexadecimal.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.numeric.float.hexadecimal.lua.jpg?raw=true) | -| `punctuation.definition.string.begin.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.string.begin.lua.jpg?raw=true) | -| `punctuation.definition.string.end.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.string.end.lua.jpg?raw=true) | -| `string.quoted.single.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/string.quoted.single.lua.jpg?raw=true) | -| `string.quoted.double.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/string.quoted.double.lua.jpg?raw=true) | -| `string.quoted.other.multiline.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/string.quoted.other.multiline.lua.jpg?raw=true) | -| `constant.character.escape.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.character.escape.lua.jpg?raw=true) | -| `constant.character.escape.byte.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.character.escape.byte.lua.jpg?raw=true) | -| `constant.character.escape.unicode.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/constant.character.escape.unicode.lua.jpg?raw=true) | -| `invalid.illegal.character.escape.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/invalid.illegal.character.escape.lua.jpg?raw=true) | -| `punctuation.definition.comment.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.lua.jpg?raw=true) | -| `comment.line.double-dash.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/comment.line.double-dash.lua.jpg?raw=true) | -| `punctuation.definition.comment.begin.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.begin.lua.jpg?raw=true) | -| `punctuation.definition.comment.end.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.end.lua.jpg?raw=true) | -| `comment.block.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/comment.block.lua.jpg?raw=true) | -| `keyword.control.goto.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/keyword.control.goto.lua.jpg?raw=true) | -| `string.tag.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/string.tag.lua.jpg?raw=true) | -| `punctuation.section.embedded.begin.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.section.embedded.begin.lua.jpg?raw=true) | -| `punctuation.section.embedded.end.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/punctuation.section.embedded.end.lua.jpg?raw=true) | -| `variable.language.self.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/variable.language.self.lua.jpg?raw=true) | -| `support.function.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/support.function.lua.jpg?raw=true) | -| `support.function.library.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/support.function.library.lua.jpg?raw=true) | -| `keyword.operator.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/keyword.operator.lua.jpg?raw=true) | -| `variable.other.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/variable.other.lua.jpg?raw=true) | +| `keyword.local.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/keyword.local.lua.jpg?raw=true) | +| `keyword.control.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/keyword.control.lua.jpg?raw=true) | +| `entity.name.class.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/entity.name.class.lua.jpg?raw=true) | +| `entity.name.function.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/entity.name.function.lua.jpg?raw=true) | +| `punctuation.definition.parameters.begin.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.parameters.begin.lua.jpg?raw=true) | +| `punctuation.definition.parameters.finish.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.parameters.finish.lua.jpg?raw=true) | +| `variable.parameter.function.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/variable.parameter.function.lua.jpg?raw=true) | +| `punctuation.separator.arguments.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.separator.arguments.lua.jpg?raw=true) | +| `constant.numeric.integer.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.numeric.integer.lua.jpg?raw=true) | +| `constant.numeric.float.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.numeric.float.lua.jpg?raw=true) | +| `constant.numeric.integer.hexadecimal.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.numeric.integer.hexadecimal.lua.jpg?raw=true) | +| `constant.numeric.float.hexadecimal.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.numeric.float.hexadecimal.lua.jpg?raw=true) | +| `punctuation.definition.string.begin.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.string.begin.lua.jpg?raw=true) | +| `punctuation.definition.string.end.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.string.end.lua.jpg?raw=true) | +| `string.quoted.single.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/string.quoted.single.lua.jpg?raw=true) | +| `string.quoted.double.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/string.quoted.double.lua.jpg?raw=true) | +| `string.quoted.other.multiline.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/string.quoted.other.multiline.lua.jpg?raw=true) | +| `constant.character.escape.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.character.escape.lua.jpg?raw=true) | +| `constant.character.escape.byte.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.character.escape.byte.lua.jpg?raw=true) | +| `constant.character.escape.unicode.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/constant.character.escape.unicode.lua.jpg?raw=true) | +| `invalid.illegal.character.escape.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/invalid.illegal.character.escape.lua.jpg?raw=true) | +| `punctuation.definition.comment.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.lua.jpg?raw=true) | +| `comment.line.double-dash.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/comment.line.double-dash.lua.jpg?raw=true) | +| `punctuation.definition.comment.begin.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.begin.lua.jpg?raw=true) | +| `punctuation.definition.comment.end.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.definition.comment.end.lua.jpg?raw=true) | +| `comment.block.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/comment.block.lua.jpg?raw=true) | +| `keyword.control.goto.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/keyword.control.goto.lua.jpg?raw=true) | +| `string.tag.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/string.tag.lua.jpg?raw=true) | +| `punctuation.section.embedded.begin.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.section.embedded.begin.lua.jpg?raw=true) | +| `punctuation.section.embedded.end.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/punctuation.section.embedded.end.lua.jpg?raw=true) | +| `variable.language.self.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/variable.language.self.lua.jpg?raw=true) | +| `support.function.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/support.function.lua.jpg?raw=true) | +| `support.function.library.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/support.function.library.lua.jpg?raw=true) | +| `keyword.operator.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/keyword.operator.lua.jpg?raw=true) | +| `variable.other.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/variable.other.lua.jpg?raw=true) | ## Semantic Tokens @@ -48,10 +48,10 @@ Preview in `Dark+` of VSCode | semantic token | fallen syntax token | preview | | :---- | :---- | :---- | -| `namespace.static` | `support.function.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/namespace.static.jpg?raw=true) | -| `namespace.readonly` | `constant.language.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/namespace.readonly.jpg?raw=true) | -| `namespace.deprecated` | `entity.name.label` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/namespace.deprecated.jpg?raw=true) | -| `parameter.declaration` | `variable.parameter` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/parameter.declaration.jpg?raw=true) | -| `property.declaration` | `entity.other.attribute` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/property.declaration.jpg?raw=true) | -| `variable` | `variable.other.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/variable.jpg?raw=true) | -| `interface.declaration` | `entity.name.function.lua` | ![avatar](https://github.com/sumneko/vscode-lua/blob/master/images/tokens/interface.declaration.jpg?raw=true) | +| `namespace.static` | `support.function.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/namespace.static.jpg?raw=true) | +| `namespace.readonly` | `constant.language.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/namespace.readonly.jpg?raw=true) | +| `namespace.deprecated` | `entity.name.label` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/namespace.deprecated.jpg?raw=true) | +| `parameter.declaration` | `variable.parameter` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/parameter.declaration.jpg?raw=true) | +| `property.declaration` | `entity.other.attribute` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/property.declaration.jpg?raw=true) | +| `variable` | `variable.other.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/variable.jpg?raw=true) | +| `interface.declaration` | `entity.name.function.lua` | ![avatar](https://github.com/LuaLS/vscode-lua/blob/master/images/tokens/interface.declaration.jpg?raw=true) | diff --git a/tools/build-3rd-meta.lua b/tools/build-3rd-meta.lua index ab6b683e4..38f693901 100644 --- a/tools/build-3rd-meta.lua +++ b/tools/build-3rd-meta.lua @@ -2,3 +2,4 @@ package.path = package.path .. ';script/?.lua;tools/?.lua' dofile 'tools/love-api.lua' dofile 'tools/lovr-api.lua' +dofile 'tools/normalize-3rd-path.lua' diff --git a/tools/build-doc.lua b/tools/build-doc.lua index fbf5fa6be..783a53113 100644 --- a/tools/build-doc.lua +++ b/tools/build-doc.lua @@ -53,29 +53,37 @@ local function getDesc(lang, desc) return locale[id] end -local function buildType(md, lang, conf) - md:add('md', '## type') +local function view(conf) if type(conf.type) == 'table' then - md:add('ts', ('%s | %s'):format(conf.type[1], conf.type[2])) + local subViews = {} + for i = 1, #conf.type do + subViews[i] = conf.type[i] + end + return table.concat(subViews, ' | ') elseif conf.type == 'array' then - md:add('ts', ('Array<%s>'):format(conf.items.type)) + return ('Array<%s>'):format(view(conf.items)) elseif conf.type == 'object' then if conf.properties then local _, first = next(conf.properties) assert(first) - md:add('ts', ('object'):format(first.type)) + return ('object'):format(view(first)) elseif conf.patternProperties then local _, first = next(conf.patternProperties) assert(first) - md:add('ts', ('Object'):format(first.type)) + return ('Object'):format(view(first)) else - md:add('ts', '**Unknown object type!!**') + return '**Unknown object type!!**' end else - md:add('ts', ('%s'):format(conf.type)) + return tostring(conf.type) end end +local function buildType(md, lang, conf) + md:add('md', '## type') + md:add('ts', view(conf)) +end + local function buildDesc(md, lang, conf) local desc = conf.markdownDescription or conf.description desc = getDesc(lang, desc) diff --git a/tools/configuration.lua b/tools/configuration.lua index 285ac3341..10bf6e08a 100644 --- a/tools/configuration.lua +++ b/tools/configuration.lua @@ -32,8 +32,10 @@ local function getDefault(temp) if default == nil and temp.hasDefault then default = json.null end - if type(default) == 'table' and getType(temp) == 'object' then - setmetatable(default, json.object) + if type(default) == 'table' + and not next(default) + and getType(temp) == 'object' then + default = json.createEmptyObject() end return default end @@ -120,6 +122,4 @@ for name, temp in pairs(template) do ::CONTINUE:: end -config['Lua.telemetry.enable'].tags = { 'telemetry' } - return config diff --git a/tools/love-api.lua b/tools/love-api.lua index 7300ccc3a..322393bf2 100644 --- a/tools/love-api.lua +++ b/tools/love-api.lua @@ -51,19 +51,19 @@ local function formatIndex(key) end local function getOptional(param) - if param.type == 'table' then - if not param.table then - return '' - end - for _, field in ipairs(param.table) do - if field.default == nil then - return '' - end - end - return '?' - else - return (param.default ~= nil) and '?' or '' - end + if param.type == 'table' then + if not param.table then + return '' + end + for _, field in ipairs(param.table) do + if field.default == nil then + return '' + end + end + return '?' + else + return (param.default ~= nil) and '?' or '' + end end local buildType @@ -101,7 +101,11 @@ local function buildMD(desc) :gsub('%. ', '.\n---\n---') end -local function buildDescription(desc, notes) +---@param desc any +---@param notes any +---@param wikiPage string? +---@return string +local function buildDescription(desc, notes, wikiPage) local lines = {} if desc then lines[#lines+1] = '---' @@ -114,6 +118,11 @@ local function buildDescription(desc, notes) lines[#lines+1] = '---' .. buildMD(notes) lines[#lines+1] = '---' end + if wikiPage then + lines[#lines+1] = '---' + lines[#lines+1] = ("---[Open in Browser](https://love2d.org/wiki/%s)"):format(wikiPage) + lines[#lines+1] = '---' + end return table.concat(lines, '\n') end @@ -128,7 +137,7 @@ local function buildDocFunc(variant, overload) params[#params+1] = '...' else if param.name:find '^[\'"]' then - params[#params+1] = ('%s%s: %s|%s'):format(param.name, getOptional(param), getTypeName(param.type), param.name) + params[#params+1] = ('%s%s: %s|%s'):format(param.name:sub(2, -2), getOptional(param), getTypeName(param.type), param.name) else params[#params+1] = ('%s%s: %s'):format(param.name, getOptional(param), getTypeName(param.type)) end @@ -153,7 +162,7 @@ end local function buildFunction(func, node, typeName) local text = {} - text[#text+1] = buildDescription(func.description, func.notes) + text[#text+1] = buildDescription(func.description, func.notes, node..func.name) for i = 2, #func.variants do local variant = func.variants[i] text[#text+1] = ('---@overload %s'):format(buildDocFunc(variant, typeName)) @@ -196,7 +205,7 @@ local function buildFunction(func, node, typeName) end local function buildFile(class, defs) - local filePath = libraryPath / (class .. '.lua') + local filePath = libraryPath / (class:gsub('%.', '/') .. '.lua') local text = {} text[#text+1] = '---@meta' @@ -204,7 +213,7 @@ local function buildFile(class, defs) if defs.version then text[#text+1] = ('-- version: %s'):format(defs.version) end - text[#text+1] = buildDescription(defs.description, defs.notes) + text[#text+1] = buildDescription(defs.description, defs.notes, class) text[#text+1] = ('---@class %s'):format(class) text[#text+1] = ('%s = {}'):format(class) @@ -216,7 +225,7 @@ local function buildFile(class, defs) for _, tp in ipairs(defs.types or {}) do local mark = {} text[#text+1] = '' - text[#text+1] = buildDescription(tp.description, tp.notes) + text[#text+1] = buildDescription(tp.description, tp.notes, class) text[#text+1] = ('---@class %s%s'):format(getTypeName(tp.name), buildSuper(tp)) text[#text+1] = ('local %s = {}'):format(tp.name) for _, func in ipairs(tp.functions or {}) do @@ -236,7 +245,7 @@ local function buildFile(class, defs) for _, enum in ipairs(defs.enums or {}) do text[#text+1] = '' - text[#text+1] = buildDescription(enum.description, enum.notes) + text[#text+1] = buildDescription(enum.description, enum.notes, enum.name) text[#text+1] = ('---@alias %s'):format(getTypeName(enum.name)) for _, constant in ipairs(enum.constants) do text[#text+1] = buildDescription(constant.description, constant.notes) @@ -251,6 +260,7 @@ local function buildFile(class, defs) text[#text+1] = '' + fs.create_directories(filePath:parent_path()) fsu.saveFile(filePath, table.concat(text, '\n')) end diff --git a/tools/lovr-api.lua b/tools/lovr-api.lua index f045afa87..573eea9ed 100644 --- a/tools/lovr-api.lua +++ b/tools/lovr-api.lua @@ -52,19 +52,19 @@ local function formatIndex(key) end local function getOptional(param) - if param.type == 'table' then - if not param.table then - return '' - end - for _, field in ipairs(param.table) do - if field.default == nil then - return '' - end - end - return '?' - else - return (param.default ~= nil) and '?' or '' - end + if param.type == 'table' then + if not param.table then + return '' + end + for _, field in ipairs(param.table) do + if field.default == nil then + return '' + end + end + return '?' + else + return (param.default ~= nil) and '?' or '' + end end local buildType @@ -132,7 +132,7 @@ local function buildDocFunc(variant, overload) params[#params+1] = '...' else if param.name:find '^[\'"]' then - params[#params+1] = ('%s%s: %s|%s'):format(param.name, getOptional(param), getTypeName(param.type), param.name) + params[#params+1] = ('%s%s: %s|%s'):format(param.name:sub(2, -2), getOptional(param), getTypeName(param.type), param.name) else params[#params+1] = ('%s%s: %s'):format(param.name, getOptional(param), getTypeName(param.type)) end @@ -200,7 +200,7 @@ end local function buildFile(defs) local class = defs.key - local filePath = libraryPath / (class .. '.lua') + local filePath = libraryPath / (class:gsub('%.', '/') .. '.lua') local text = {} text[#text+1] = '---@meta' @@ -246,6 +246,7 @@ local function buildFile(defs) text[#text+1] = '' + fs.create_directories(filePath:parent_path()) fsu.saveFile(filePath, table.concat(text, '\n')) end diff --git a/tools/normalize-3rd-path.lua b/tools/normalize-3rd-path.lua new file mode 100644 index 000000000..9a9194cf9 --- /dev/null +++ b/tools/normalize-3rd-path.lua @@ -0,0 +1,17 @@ +local fs = require 'bee.filesystem' +local fsu = require 'fs-utility' + +local thirdPath = fs.path 'meta/3rd' + +for dir in fs.pairs(thirdPath) do + local libraryPath = dir / 'library' + if fs.is_directory(libraryPath) then + fsu.scanDirectory(libraryPath, function (fullPath) + if fullPath:stem():string():find '%.' then + local newPath = fullPath:parent_path() / (fullPath:stem():string():gsub('%.', '/') .. '.lua') + fs.create_directories(newPath:parent_path()) + fs.rename(fullPath, newPath) + end + end) + end +end