From 51714cabb6794cb692e1afad72549946609acd44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Ritzl?= <bjorn.ritzl@gmail.com>
Date: Fri, 9 Dec 2022 16:02:16 +0100
Subject: [PATCH 01/48] Use LuaCheck as a diagnostics module (#1)

* Add luacheck

* Create luacheck.lua

* Update diagnostic.lua

* Removed

* Update changelog.md

* Update make.lua
---
 .github/Alipay.jpg                            | Bin 123705 -> 0 bytes
 .github/FUNDING.yml                           |   1 -
 .github/WeChat Pay.jpg                        | Bin 98867 -> 0 bytes
 changelog.md                                  |   3 +
 make.lua                                      |   1 +
 script/core/diagnostics/luacheck.lua          |  36 +
 script/luacheck/builtin_standards/init.lua    | 301 ++++++
 script/luacheck/builtin_standards/love.lua    | 137 +++
 script/luacheck/builtin_standards/ngx.lua     | 152 +++
 script/luacheck/cache.lua                     | 139 +++
 script/luacheck/check.lua                     |  96 ++
 script/luacheck/check_state.lua               |  66 ++
 script/luacheck/config.lua                    | 542 ++++++++++
 script/luacheck/core_utils.lua                |  95 ++
 script/luacheck/decoder.lua                   | 199 ++++
 script/luacheck/expand_rockspec.lua           | 110 ++
 script/luacheck/filter.lua                    | 535 ++++++++++
 script/luacheck/format.lua                    | 334 ++++++
 script/luacheck/fs.lua                        | 219 ++++
 script/luacheck/globbing.lua                  | 161 +++
 script/luacheck/init.lua                      | 135 +++
 script/luacheck/lexer.lua                     | 749 +++++++++++++
 script/luacheck/main.lua                      | 353 +++++++
 script/luacheck/multithreading.lua            | 105 ++
 script/luacheck/options.lua                   | 419 ++++++++
 script/luacheck/parser.lua                    | 984 ++++++++++++++++++
 script/luacheck/profiler.lua                  | 149 +++
 script/luacheck/runner.lua                    | 415 ++++++++
 script/luacheck/serializer.lua                | 211 ++++
 .../luacheck/stages/detect_bad_whitespace.lua |  76 ++
 .../stages/detect_cyclomatic_complexity.lua   | 159 +++
 .../luacheck/stages/detect_empty_blocks.lua   |  36 +
 .../stages/detect_empty_statements.lua        |  13 +
 script/luacheck/stages/detect_globals.lua     | 252 +++++
 .../stages/detect_reversed_fornum_loops.lua   |  39 +
 .../stages/detect_unbalanced_assignments.lua  |  34 +
 .../stages/detect_uninit_accesses.lua         |  54 +
 .../stages/detect_unreachable_code.lua        |  36 +
 .../luacheck/stages/detect_unused_fields.lua  |  81 ++
 .../luacheck/stages/detect_unused_locals.lua  | 330 ++++++
 script/luacheck/stages/init.lua               |  77 ++
 script/luacheck/stages/linearize.lua          | 708 +++++++++++++
 script/luacheck/stages/name_functions.lua     |  71 ++
 script/luacheck/stages/parse.lua              |  19 +
 .../luacheck/stages/parse_inline_options.lua  | 351 +++++++
 script/luacheck/stages/resolve_locals.lua     | 216 ++++
 script/luacheck/stages/unwrap_parens.lua      |  52 +
 script/luacheck/standards.lua                 | 269 +++++
 script/luacheck/unicode.lua                   |  36 +
 .../unicode_printability_boundaries.lua       |   2 +
 script/luacheck/utils.lua                     | 377 +++++++
 script/luacheck/vendor/sha1/LICENSE           |  21 +
 script/luacheck/vendor/sha1/bit32_ops.lua     |  24 +
 script/luacheck/vendor/sha1/bit_ops.lua       |  24 +
 script/luacheck/vendor/sha1/common.lua        |  20 +
 script/luacheck/vendor/sha1/init.lua          | 195 ++++
 script/luacheck/vendor/sha1/lua53_ops.lua     |  29 +
 script/luacheck/vendor/sha1/pure_lua_ops.lua  | 144 +++
 script/luacheck/version.lua                   |  36 +
 script/proto/diagnostic.lua                   | 290 +++---
 60 files changed, 10576 insertions(+), 142 deletions(-)
 delete mode 100644 .github/Alipay.jpg
 delete mode 100644 .github/FUNDING.yml
 delete mode 100644 .github/WeChat Pay.jpg
 create mode 100644 script/core/diagnostics/luacheck.lua
 create mode 100755 script/luacheck/builtin_standards/init.lua
 create mode 100755 script/luacheck/builtin_standards/love.lua
 create mode 100755 script/luacheck/builtin_standards/ngx.lua
 create mode 100755 script/luacheck/cache.lua
 create mode 100755 script/luacheck/check.lua
 create mode 100755 script/luacheck/check_state.lua
 create mode 100755 script/luacheck/config.lua
 create mode 100755 script/luacheck/core_utils.lua
 create mode 100755 script/luacheck/decoder.lua
 create mode 100755 script/luacheck/expand_rockspec.lua
 create mode 100755 script/luacheck/filter.lua
 create mode 100755 script/luacheck/format.lua
 create mode 100755 script/luacheck/fs.lua
 create mode 100755 script/luacheck/globbing.lua
 create mode 100755 script/luacheck/init.lua
 create mode 100755 script/luacheck/lexer.lua
 create mode 100755 script/luacheck/main.lua
 create mode 100755 script/luacheck/multithreading.lua
 create mode 100755 script/luacheck/options.lua
 create mode 100755 script/luacheck/parser.lua
 create mode 100755 script/luacheck/profiler.lua
 create mode 100755 script/luacheck/runner.lua
 create mode 100755 script/luacheck/serializer.lua
 create mode 100755 script/luacheck/stages/detect_bad_whitespace.lua
 create mode 100755 script/luacheck/stages/detect_cyclomatic_complexity.lua
 create mode 100755 script/luacheck/stages/detect_empty_blocks.lua
 create mode 100755 script/luacheck/stages/detect_empty_statements.lua
 create mode 100755 script/luacheck/stages/detect_globals.lua
 create mode 100755 script/luacheck/stages/detect_reversed_fornum_loops.lua
 create mode 100755 script/luacheck/stages/detect_unbalanced_assignments.lua
 create mode 100755 script/luacheck/stages/detect_uninit_accesses.lua
 create mode 100755 script/luacheck/stages/detect_unreachable_code.lua
 create mode 100755 script/luacheck/stages/detect_unused_fields.lua
 create mode 100755 script/luacheck/stages/detect_unused_locals.lua
 create mode 100755 script/luacheck/stages/init.lua
 create mode 100755 script/luacheck/stages/linearize.lua
 create mode 100755 script/luacheck/stages/name_functions.lua
 create mode 100755 script/luacheck/stages/parse.lua
 create mode 100755 script/luacheck/stages/parse_inline_options.lua
 create mode 100755 script/luacheck/stages/resolve_locals.lua
 create mode 100755 script/luacheck/stages/unwrap_parens.lua
 create mode 100755 script/luacheck/standards.lua
 create mode 100755 script/luacheck/unicode.lua
 create mode 100755 script/luacheck/unicode_printability_boundaries.lua
 create mode 100755 script/luacheck/utils.lua
 create mode 100755 script/luacheck/vendor/sha1/LICENSE
 create mode 100755 script/luacheck/vendor/sha1/bit32_ops.lua
 create mode 100755 script/luacheck/vendor/sha1/bit_ops.lua
 create mode 100755 script/luacheck/vendor/sha1/common.lua
 create mode 100755 script/luacheck/vendor/sha1/init.lua
 create mode 100755 script/luacheck/vendor/sha1/lua53_ops.lua
 create mode 100755 script/luacheck/vendor/sha1/pure_lua_ops.lua
 create mode 100755 script/luacheck/version.lua

diff --git a/.github/Alipay.jpg b/.github/Alipay.jpg
deleted file mode 100644
index c76aa0fe765fccd28aaa309319960eaad597f451..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 0bb446b1b..000000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1 +0,0 @@
-custom: ["https://paypal.me/sumneko", "https://github.com/sumneko/lua-language-server/issues/484"]
diff --git a/.github/WeChat Pay.jpg b/.github/WeChat Pay.jpg
deleted file mode 100644
index 32ce87223646345ac65038adebfa8fa6341930e9..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/changelog.md b/changelog.md
index 5d35d36e0..b48657815 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,8 @@
 # changelog
 
+## Unreleased
+* `NEW` Added LuaCheck
+
 ## 3.6.5
 * `FIX` [#831]
 * `FIX` [#1729]
diff --git a/make.lua b/make.lua
index c34905d62..3caca4f50 100644
--- a/make.lua
+++ b/make.lua
@@ -1,5 +1,6 @@
 local lm = require 'luamake'
 
+lm.notest = true
 lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
new file mode 100644
index 000000000..a1d7d2a13
--- /dev/null
+++ b/script/core/diagnostics/luacheck.lua
@@ -0,0 +1,36 @@
+local files   = require 'files'
+local guide   = require 'parser.guide'
+local lang    = require 'language'
+local define  = require 'proto.define'
+local log     = require 'log'
+local luacheck = require "luacheck.check"
+local lcstages = require "luacheck.stages"
+local lcformat = require "luacheck.format"
+local lcfilter = require "luacheck.filter"
+
+local function pprint(t)
+    if type(t) ~= "table" then
+        log.info(t)
+        return
+    end
+    for k,v in pairs(t) do
+        log.info("t k:", k, "v:", v)
+    end
+end
+
+return function (uri, callback)
+    local text = files.getText(uri)
+    local report = luacheck(text)
+    lcfilter.filter({report})
+    for i,warning in ipairs(report.filtered_warnings) do
+        local message = lcformat.get_message(warning)
+        local start = ((warning.line - 1) * 10000) + warning.column - 1
+        local finish = ((warning.line - 1) * 10000) + warning.end_column
+        callback({
+            start   = start,
+            finish  = finish,
+            tags    = { define.DiagnosticTag.Unnecessary },
+            message = message,
+        })
+    end
+end
diff --git a/script/luacheck/builtin_standards/init.lua b/script/luacheck/builtin_standards/init.lua
new file mode 100755
index 000000000..9805b4ac6
--- /dev/null
+++ b/script/luacheck/builtin_standards/init.lua
@@ -0,0 +1,301 @@
+local love = require "luacheck.builtin_standards.love"
+local ngx = require "luacheck.builtin_standards.ngx"
+local standards = require "luacheck.standards"
+
+local builtin_standards = {}
+
+local function def_to_std(def)
+   return {read_globals = def.fields}
+end
+
+local function add_defs(...)
+   local res = {}
+
+   for _, def in ipairs({...}) do
+      standards.add_std_table(res, def_to_std(def))
+   end
+
+   return res
+end
+
+local empty = {}
+
+local string_defs = {}
+
+string_defs.min = standards.def_fields("byte", "char", "dump", "find", "format", "gmatch",
+   "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper")
+
+string_defs.lua51 = add_defs(string_defs.min, standards.def_fields("gfind"))
+string_defs.lua52 = string_defs.min
+string_defs.lua53 = add_defs(string_defs.min, standards.def_fields("pack", "packsize", "unpack"))
+string_defs.luajit = string_defs.lua51
+
+local file_defs = {}
+
+file_defs.min = {
+   fields = {
+      __gc = empty,
+      __index = {other_fields = true},
+      __tostring = empty,
+      close = empty,
+      flush = empty,
+      lines = empty,
+      read = empty,
+      seek = empty,
+      setvbuf = empty,
+      write = empty
+   }
+}
+
+file_defs.lua51 = file_defs.min
+file_defs.lua52 = file_defs.min
+file_defs.lua53 = add_defs(file_defs.min, {fields = {__name = string_defs.lua53}})
+file_defs.luajit = file_defs.min
+
+local function make_min_def(method_defs)
+   local string_def = string_defs[method_defs]
+   local file_def = file_defs[method_defs]
+
+   return {
+      fields = {
+         _G = {other_fields = true, read_only = false},
+         _VERSION = string_def,
+         arg = {other_fields = true},
+         assert = empty,
+         collectgarbage = empty,
+         coroutine = standards.def_fields("create", "resume", "running", "status", "wrap", "yield"),
+         debug = standards.def_fields("debug", "gethook", "getinfo", "getlocal", "getmetatable", "getregistry",
+            "getupvalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback"),
+         dofile = empty,
+         error = empty,
+         getmetatable = empty,
+         io = {
+            fields = {
+               close = empty,
+               flush = empty,
+               input = empty,
+               lines = empty,
+               open = empty,
+               output = empty,
+               popen = empty,
+               read = empty,
+               stderr = file_def,
+               stdin = file_def,
+               stdout = file_def,
+               tmpfile = empty,
+               type = empty,
+               write = empty
+            }
+         },
+         ipairs = empty,
+         load = empty,
+         loadfile = empty,
+         math = standards.def_fields("abs", "acos", "asin", "atan", "ceil", "cos",
+            "deg", "exp", "floor", "fmod", "huge", "log",
+            "max", "min", "modf", "pi", "rad", "random", "randomseed",
+            "sin", "sqrt", "tan"),
+         next = empty,
+         os = standards.def_fields("clock", "date", "difftime", "execute", "exit", "getenv",
+            "remove", "rename", "setlocale", "time", "tmpname"),
+         package = {
+            fields = {
+               config = string_def,
+               cpath = {fields = string_def.fields, read_only = false},
+               loaded = {other_fields = true, read_only = false},
+               loadlib = empty,
+               path = {fields = string_def.fields, read_only = false},
+               preload = {other_fields = true, read_only = false}
+            }
+         },
+         pairs = empty,
+         pcall = empty,
+         print = empty,
+         rawequal = empty,
+         rawget = empty,
+         rawset = empty,
+         require = empty,
+         select = empty,
+         setmetatable = empty,
+         string = string_def,
+         table = standards.def_fields("concat", "insert", "remove", "sort"),
+         tonumber = empty,
+         tostring = empty,
+         type = empty,
+         xpcall = empty
+      }
+   }
+end
+
+local bit32_def = standards.def_fields("arshift", "band", "bnot", "bor", "btest", "bxor", "extract",
+   "lrotate", "lshift", "replace", "rrotate", "rshift")
+
+local lua_defs = {}
+
+lua_defs.min = make_min_def("min")
+lua_defs.lua51 = add_defs(make_min_def("lua52"), {
+   fields = {
+      debug = standards.def_fields("getfenv", "setfenv"),
+      getfenv = empty,
+      loadstring = empty,
+      math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "pow", "sinh", "tanh"),
+      module = empty,
+      newproxy = empty,
+      package = {
+         fields = {
+            loaders = {other_fields = true, read_only = false},
+            seeall = empty
+         }
+      },
+      setfenv = empty,
+      table = standards.def_fields("maxn"),
+      unpack = empty
+   }
+})
+lua_defs.lua51c = add_defs(lua_defs.lua51, make_min_def("lua51"), {
+   fields = {
+      gcinfo = empty,
+      math = standards.def_fields("mod"),
+      table = standards.def_fields("foreach", "foreachi", "getn", "setn")
+   }
+})
+lua_defs.lua52 = add_defs(make_min_def("lua52"), {
+   fields = {
+      _ENV = {other_fields = true, read_only = false},
+      bit32 = bit32_def,
+      debug = standards.def_fields("getuservalue", "setuservalue", "upvalueid", "upvaluejoin"),
+      math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "pow", "sinh", "tanh"),
+      package = {
+         fields = {
+            searchers = {other_fields = true, read_only = false},
+            searchpath = empty
+         }
+      },
+      rawlen = empty,
+      table = standards.def_fields("pack", "unpack")
+   }
+})
+lua_defs.lua52c = add_defs(lua_defs.lua52, {
+   fields = {
+      loadstring = empty,
+      math = standards.def_fields("log10"),
+      module = empty,
+      package = {
+         fields = {
+            loaders = {other_fields = true, read_only = false},
+            seeall = empty
+         }
+      },
+      table = standards.def_fields("maxn"),
+      unpack = empty
+   }
+})
+lua_defs.lua53 = add_defs(make_min_def("lua53"), {
+   fields = {
+      _ENV = {other_fields = true, read_only = false},
+      coroutine = standards.def_fields("isyieldable"),
+      debug = standards.def_fields("getuservalue", "setuservalue", "upvalueid", "upvaluejoin"),
+      math = standards.def_fields("maxinteger", "mininteger", "tointeger", "type", "ult"),
+      package = {
+         fields = {
+            searchers = {other_fields = true, read_only = false},
+            searchpath = empty
+         }
+      },
+      rawlen = empty,
+      table = standards.def_fields("move", "pack", "unpack"),
+      utf8 = {
+         fields = {
+            char = empty,
+            charpattern = string_defs.lua53,
+            codepoint = empty,
+            codes = empty,
+            len = empty,
+            offset = empty
+         }
+      }
+   }
+})
+lua_defs.lua53c = add_defs(lua_defs.lua53, {
+   fields = {
+      bit32 = bit32_def,
+      math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "pow", "sinh", "tanh")
+   }
+})
+lua_defs.luajit = add_defs(make_min_def("luajit"), {
+   fields = {
+      bit = standards.def_fields("arshift", "band", "bnot", "bor", "bswap", "bxor", "lshift", "rol", "ror",
+         "rshift", "tobit", "tohex"),
+      coroutine = standards.def_fields("isyieldable"),
+      debug = standards.def_fields("getfenv", "setfenv", "upvalueid", "upvaluejoin"),
+      gcinfo = empty,
+      getfenv = empty,
+      jit = {other_fields = true},
+      loadstring = empty,
+      math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "mod", "pow", "sinh", "tanh"),
+      module = empty,
+      newproxy = empty,
+      package = {
+         fields = {
+            loaders = {other_fields = true, read_only = false},
+            searchpath = empty,
+            seeall = empty
+         }
+      },
+      setfenv = empty,
+      table = standards.def_fields("clear", "foreach", "foreachi", "getn", "maxn", "move", "new"),
+      unpack = empty
+   }
+})
+lua_defs.ngx_lua = add_defs(lua_defs.luajit, ngx)
+lua_defs.max = add_defs(lua_defs.lua51c, lua_defs.lua52c, lua_defs.lua53c, lua_defs.luajit)
+
+for name, def in pairs(lua_defs) do
+   builtin_standards[name] = def_to_std(def)
+end
+
+local function get_running_lua_std_name()
+   if rawget(_G, "jit") then
+      return "luajit"
+   elseif _VERSION == "Lua 5.1" then
+      return "lua51c"
+   elseif _VERSION == "Lua 5.2" then
+      return "lua52c"
+   elseif _VERSION == "Lua 5.3" then
+      return "lua53c"
+   else
+      return "max"
+   end
+end
+
+builtin_standards._G = builtin_standards[get_running_lua_std_name()]
+
+builtin_standards.busted = {
+   read_globals = {
+      "describe", "insulate", "expose", "it", "pending", "before_each", "after_each",
+      "lazy_setup", "lazy_teardown", "strict_setup", "strict_teardown", "setup", "teardown",
+      "context", "spec", "test", "assert", "spy", "mock", "stub", "finally", "randomize"
+   }
+}
+
+builtin_standards.love = love
+
+builtin_standards.rockspec = {
+   globals = {
+      "rockspec_format", "package", "version", "description", "dependencies", "supported_platforms",
+      "external_dependencies", "source", "build", "hooks", "deploy", "build_dependencies", "test_dependencies", "test"
+   }
+}
+
+builtin_standards.luacheckrc = {
+   globals = {
+      "global", "unused", "redefined", "unused_args", "unused_secondaries", "self", "compat", "allow_defined",
+      "allow_defined_top", "module", "globals", "read_globals", "new_globals", "new_read_globals", "not_globals",
+      "ignore", "enable", "only", "std", "max_line_length", "max_code_line_length", "max_string_line_length",
+      "max_comment_line_length", "max_cyclomatic_complexity", "quiet", "color", "codes", "ranges", "formatter",
+      "cache", "jobs", "files", "stds", "exclude_files", "include_files"
+   }
+}
+
+builtin_standards.none = {}
+
+return builtin_standards
diff --git a/script/luacheck/builtin_standards/love.lua b/script/luacheck/builtin_standards/love.lua
new file mode 100755
index 000000000..aa173dc53
--- /dev/null
+++ b/script/luacheck/builtin_standards/love.lua
@@ -0,0 +1,137 @@
+local standards = require "luacheck.standards"
+
+local empty = {}
+local read_write = {read_only = false}
+
+local love = {
+   fields = {
+      getVersion = empty,
+      conf = read_write,
+      directorydropped = read_write,
+      draw = read_write,
+      errhand = read_write,
+      errorhandler = read_write,
+      filedropped = read_write,
+      focus = read_write,
+      gamepadaxis = read_write,
+      gamepadpressed = read_write,
+      gamepadreleased = read_write,
+      handlers = read_write,
+      hasDeprecationOutput = empty,
+      joystickadded = read_write,
+      joystickaxis = read_write,
+      joystickhat = read_write,
+      joystickpressed = read_write,
+      joystickreleased = read_write,
+      joystickremoved = read_write,
+      keypressed = read_write,
+      keyreleased = read_write,
+      load = read_write,
+      lowmemory = read_write,
+      mousefocus = read_write,
+      mousemoved = read_write,
+      mousepressed = read_write,
+      mousereleased = read_write,
+      quit = read_write,
+      resize = read_write,
+      run = read_write,
+      setDeprecationOutput = empty,
+      textedited = read_write,
+      textinput = read_write,
+      threaderror = read_write,
+      touchmoved = read_write,
+      touchpressed = read_write,
+      touchreleased = read_write,
+      update = read_write,
+      visible = read_write,
+      wheelmoved = read_write,
+
+      audio = standards.def_fields("getDistanceModel","getDopplerScale","getSourceCount","getOrientation",
+         "getPosition","getVelocity","getVolume","newSource","pause","play","setDistanceModel","setDopplerScale",
+         "setOrientation","setPosition","setVelocity", "setVolume","stop","getActiveSourceCount","getRecordingDevices",
+         "newQueueableSource","setEffect","getActiveEffects","getEffect","getMaxSceneEffects","getMaxSourceEffects",
+         "isEffectsSupported","setMixWithSystem"),
+
+      data = standards.def_fields("compress","decode","decompress","encode","getPackedSize","hash","newByteData",
+         "newDataView","pack","unpack"),
+
+      event = standards.def_fields("clear","poll","pump","push","quit","wait"),
+
+      filesystem = standards.def_fields("append","areSymlinksEnabled","createDirectory","exists",
+         "getAppdataDirectory","getDirectoryItems","getIdentity","getLastModified",
+         "getRealDirectory","getRequirePath","getSaveDirectory","getSize","getSource",
+         "getSourceBaseDirectory","getUserDirectory","getWorkingDirectory","init","isDirectory",
+         "isFile","isFused","isSymlink","lines","load","mount","newFile","newFileData","read",
+         "remove","setIdentity","setRequirePath","setSource","setSymlinksEnabled","unmount","write",
+         "getInfo","setCRequirePath","getCRequirePath"),
+
+      font = standards.def_fields("newImageRasterizer", "newGlyphData", "newTrueTypeRasterizer",
+         "newBMFontRasterizer", "newRasterizer"),
+
+      graphics = standards.def_fields("arc","circle","clear","discard","draw","ellipse","getBackgroundColor",
+         "getBlendMode","getCanvas","getCanvasFormats","getColor","getColorMask",
+         "getDefaultFilter","getDimensions","getFont","getHeight",
+         "getLineJoin","getLineStyle","getLineWidth","getShader","getStats","getStencilTest",
+         "getSupported","getSystemLimits","getPointSize","getRendererInfo","getScissor","getWidth",
+         "intersectScissor","isGammaCorrect","isWireframe","line","newCanvas","newFont","newMesh",
+         "newImage","newImageFont","newParticleSystem","newShader","newText","newQuad",
+         "newSpriteBatch","newVideo","origin","points","polygon","pop","present",
+         "print","printf","push","rectangle","reset","rotate","scale","setBackgroundColor",
+         "setBlendMode","setCanvas","setColor","setColorMask","setDefaultFilter","setFont",
+         "setLineJoin","setLineStyle","setLineWidth","setNewFont","setShader","setPointSize",
+         "setScissor","setStencilTest","setWireframe","shear","stencil","translate",
+         "captureScreenshot","getImageFormats","newArrayImage","newVolumeImage","newCubeImage",
+         "getTextureTypes","drawLayer","setDepthMode","setMeshCullMode","setFrontFaceWinding",
+         "applyTransform","replaceTransform","transformPoint","inverseTransformPoint","getStackDepth",
+         "flushBatch","validateShader","drawInstanced","getDepthMode","getFrontFaceWinding","getMeshCullMode",
+         "getDPIScale","getPixelDimensions","getPixelHeight","getPixelWidth","isActive","getDefaultMipmapFilter",
+         "setDefaultMipmapFilter"),
+
+      image = standards.def_fields("isCompressed","newCompressedData","newImageData","newCubeFaces"),
+
+      joystick = standards.def_fields("getJoystickCount","getJoysticks","loadGamepadMappings",
+         "saveGamepadMappings","setGamepadMapping"),
+
+      keyboard = standards.def_fields("getKeyFromScancode","getScancodeFromKey","hasKeyRepeat","hasTextInput",
+         "isDown","isScancodeDown","setKeyRepeat","setTextInput","hasScreenKeyboard"),
+
+      math = standards.def_fields("compress","decompress","gammaToLinear","getRandomSeed","getRandomState",
+         "isConvex","linearToGamma","newBezierCurve","newRandomGenerator","noise","random",
+         "randomNormal","setRandomSeed","setRandomState","triangulate","newTransform"),
+
+      mouse = standards.def_fields("getCursor","getPosition","getRelativeMode","getSystemCursor","getX",
+         "getY","hasCursor","isDown","isGrabbed","isVisible","newCursor","setCursor","setGrabbed",
+         "setPosition","setRelativeMode","setVisible","setX","setY","isCursorSupported"),
+
+      physics = standards.def_fields("getDistance","getMeter","newBody","newChainShape","newCircleShape",
+         "newDistanceJoint","newEdgeShape","newFixture","newFrictionJoint","newGearJoint",
+         "newMotorJoint","newMouseJoint","newPolygonShape","newPrismaticJoint","newPulleyJoint",
+         "newRectangleShape","newRevoluteJoint","newRopeJoint","newWeldJoint","newWheelJoint",
+         "newWorld","setMeter"),
+
+      sound = standards.def_fields("newDecoder","newSoundData"),
+
+      system = standards.def_fields("getClipboardText","getOS","getPowerInfo","getProcessorCount","openURL",
+         "setClipboardText","vibrate","hasBackgroundMusic"),
+
+      thread = standards.def_fields("getChannel","newChannel","newThread"),
+
+      timer = standards.def_fields("getAverageDelta","getDelta","getFPS","getTime","sleep","step"),
+
+      touch = standards.def_fields("getPosition","getPressure","getTouches"),
+
+      video = standards.def_fields("newVideoStream"),
+
+      window = standards.def_fields("close","fromPixels","getDisplayName","getFullscreen",
+         "getFullscreenModes","getIcon","getMode","getPixelScale","getPosition","getTitle",
+         "hasFocus","hasMouseFocus","isDisplaySleepEnabled","isMaximized","isOpen","isVisible",
+         "maximize","minimize","requestAttention","setDisplaySleepEnabled","setFullscreen",
+         "setIcon","setMode","setPosition","setTitle","showMessageBox","toPixels",
+         "updateMode","isMinimized","restore","getDPIScale")
+   }
+}
+
+-- `love` standard contains only `love` global, so return it here directly using normal std format.
+return {
+   read_globals = {love = love}
+}
diff --git a/script/luacheck/builtin_standards/ngx.lua b/script/luacheck/builtin_standards/ngx.lua
new file mode 100755
index 000000000..a1b44fce6
--- /dev/null
+++ b/script/luacheck/builtin_standards/ngx.lua
@@ -0,0 +1,152 @@
+local standards = require "luacheck.standards"
+
+local empty = {}
+
+local luajit_string_def = standards.def_fields("byte", "char", "dump", "find", "format", "gmatch",
+   "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper")
+
+-- Globals added by lua-nginx-module 0.10.10 in internal definition table format.
+-- Will be added to `luajit` std to form `ngx_lua` std.
+local ngx_defs = {
+   fields = {
+      ngx = {
+         fields = {
+            arg = {other_fields = true, read_only = false},
+            var = {other_fields = true, read_only = false},
+            OK = empty,
+            ERROR = empty,
+            AGAIN = empty,
+            DONE = empty,
+            DECLINED = empty,
+            null = empty,
+            HTTP_GET = empty,
+            HTTP_HEAD = empty,
+            HTTP_PUT = empty,
+            HTTP_POST = empty,
+            HTTP_DELETE = empty,
+            HTTP_OPTIONS = empty,
+            HTTP_MKCOL = empty,
+            HTTP_COPY = empty,
+            HTTP_MOVE = empty,
+            HTTP_PROPFIND = empty,
+            HTTP_PROPPATCH = empty,
+            HTTP_LOCK = empty,
+            HTTP_UNLOCK = empty,
+            HTTP_PATCH = empty,
+            HTTP_TRACE = empty,
+            HTTP_CONTINUE = empty,
+            HTTP_SWITCHING_PROTOCOLS = empty,
+            HTTP_OK = empty,
+            HTTP_CREATED = empty,
+            HTTP_ACCEPTED = empty,
+            HTTP_NO_CONTENT = empty,
+            HTTP_PARTIAL_CONTENT = empty,
+            HTTP_SPECIAL_RESPONSE = empty,
+            HTTP_MOVED_PERMANENTLY = empty,
+            HTTP_MOVED_TEMPORARILY = empty,
+            HTTP_SEE_OTHER = empty,
+            HTTP_NOT_MODIFIED = empty,
+            HTTP_TEMPORARY_REDIRECT = empty,
+            HTTP_BAD_REQUEST = empty,
+            HTTP_UNAUTHORIZED = empty,
+            HTTP_PAYMENT_REQUIRED = empty,
+            HTTP_FORBIDDEN = empty,
+            HTTP_NOT_FOUND = empty,
+            HTTP_NOT_ALLOWED = empty,
+            HTTP_NOT_ACCEPTABLE = empty,
+            HTTP_REQUEST_TIMEOUT = empty,
+            HTTP_CONFLICT = empty,
+            HTTP_GONE = empty,
+            HTTP_UPGRADE_REQUIRED = empty,
+            HTTP_TOO_MANY_REQUESTS = empty,
+            HTTP_CLOSE = empty,
+            HTTP_ILLEGAL = empty,
+            HTTP_INTERNAL_SERVER_ERROR = empty,
+            HTTP_METHOD_NOT_IMPLEMENTED = empty,
+            HTTP_BAD_GATEWAY = empty,
+            HTTP_SERVICE_UNAVAILABLE = empty,
+            HTTP_GATEWAY_TIMEOUT = empty,
+            HTTP_VERSION_NOT_SUPPORTED = empty,
+            HTTP_INSUFFICIENT_STORAGE = empty,
+            STDERR = empty,
+            EMERG = empty,
+            ALERT = empty,
+            CRIT = empty,
+            ERR = empty,
+            WARN = empty,
+            NOTICE = empty,
+            INFO = empty,
+            DEBUG = empty,
+            ctx = {other_fields = true, read_only = false},
+            location = standards.def_fields("capture", "capture_multi"),
+            status = {read_only = false},
+            header = {other_fields = true, read_only = false},
+            resp = standards.def_fields("get_headers"),
+            req = standards.def_fields("is_internal", "start_time", "http_version", "raw_header",
+               "get_method", "set_method", "set_uri", "set_uri_args", "get_uri_args",
+               "get_post_args", "get_headers", "set_header", "clear_header", "read_body",
+               "discard_body", "get_body_data", "get_body_file", "set_body_data",
+               "set_body_file", "init_body", "append_body", "finish_body", "socket"),
+            exec = empty,
+            redirect = empty,
+            send_headers = empty,
+            headers_sent = empty,
+            print = empty,
+            say = empty,
+            log = empty,
+            flush = empty,
+            exit = empty,
+            eof = empty,
+            sleep = empty,
+            escape_uri = empty,
+            unescape_uri = empty,
+            encode_args = empty,
+            decode_args = empty,
+            encode_base64 = empty,
+            decode_base64 = empty,
+            crc32_short = empty,
+            crc32_long = empty,
+            hmac_sha1 = empty,
+            md5 = empty,
+            md5_bin = empty,
+            sha1_bin = empty,
+            quote_sql_str = empty,
+            today = empty,
+            time = empty,
+            now = empty,
+            update_time = empty,
+            localtime = empty,
+            utctime = empty,
+            cookie_time = empty,
+            http_time = empty,
+            parse_http_time = empty,
+            is_subrequest = empty,
+            re = standards.def_fields("match", "find", "gmatch", "sub", "gsub"),
+            shared = {other_fields = true, read_only = false},
+            socket = standards.def_fields("udp", "tcp", "connect", "stream"),
+            get_phase = empty,
+            thread = standards.def_fields("spawn", "wait", "kill"),
+            on_abort = empty,
+            timer = standards.def_fields("at", "every", "running_count", "pending_count"),
+            config = {
+               fields = {
+                  subsystem = luajit_string_def,
+                  debug = empty,
+                  prefix = empty,
+                  nginx_version = empty,
+                  nginx_configure = empty,
+                  ngx_lua_version = empty,
+               }
+            },
+            worker = standards.def_fields("pid", "count", "id", "exiting"),
+         },
+      },
+      ndk = {
+         fields = {
+            set_var = {other_fields = true},
+         },
+      },
+   },
+}
+
+return ngx_defs
diff --git a/script/luacheck/cache.lua b/script/luacheck/cache.lua
new file mode 100755
index 000000000..fe26fe9ac
--- /dev/null
+++ b/script/luacheck/cache.lua
@@ -0,0 +1,139 @@
+local fs = require "luacheck.fs"
+local serializer = require "luacheck.serializer"
+local sha1 = require "luacheck.vendor.sha1"
+local utils = require "luacheck.utils"
+
+local cache = {}
+
+-- Check results can be cached inside a given cache directory.
+-- Check result for a file is stored in `<cache_dir>/<SHA1(filename)>`.
+-- Cache file format: <format_version>\n<filename>\n<serialized check result`.
+
+-- Returns default cache directory or nothing.
+function cache.get_default_dir()
+   if utils.is_windows then
+      local local_app_data_dir = os.getenv("LOCALAPPDATA")
+
+      if not local_app_data_dir then
+         local user_profile_dir = os.getenv("USERPROFILE")
+
+         if user_profile_dir then
+            local_app_data_dir = fs.join(user_profile_dir, "Local Settings", "Application Data")
+         end
+      end
+
+      if local_app_data_dir then
+         return fs.join(local_app_data_dir, "Luacheck", "Cache")
+      end
+   else
+      local fh = assert(io.popen("uname -s"))
+      local system = fh:read("*l")
+      fh:close()
+
+      if system == "Darwin" then
+         local home_dir = os.getenv("HOME")
+
+         if home_dir then
+            return fs.join(home_dir, "Library", "Caches", "Luacheck")
+         end
+      else
+         local config_home_dir = os.getenv("XDG_CACHE_HOME")
+
+         if not config_home_dir then
+            local home_dir = os.getenv("HOME")
+
+            if home_dir then
+               config_home_dir = fs.join(home_dir, ".cache")
+            end
+         end
+
+         if config_home_dir then
+            return fs.join(config_home_dir, "luacheck")
+         end
+      end
+   end
+end
+
+local format_version = "1"
+
+local Cache = utils.class()
+
+function Cache:__init(cache_directory)
+   local ok, err = fs.make_dirs(cache_directory)
+
+   if not ok then
+      return nil, ("Couldn't initialize cache in %s: %s"):format(cache_directory, err)
+   end
+
+   self._dir = cache_directory
+   self._current_dir = fs.get_current_dir()
+end
+
+-- Caches check result for a file. Returns true on success, nothing on error.
+function Cache:put(filename, check_result)
+   local normalized_filename = fs.normalize(fs.join(self._current_dir, filename))
+   local cache_filename = fs.join(self._dir, sha1.sha1(normalized_filename))
+
+   local fh = io.open(cache_filename, "wb")
+
+   if not fh then
+      return
+   end
+
+   local serialized_result = serializer.dump_check_result(check_result)
+   fh:write(format_version, "\n", normalized_filename, "\n", serialized_result)
+   fh:close()
+   return true
+end
+
+-- Retrieves cached check result for a file.
+-- Returns check result on cache hit, nothing on cache miss,
+-- nil and true on malformed cache data.
+function Cache:get(filename)
+   local normalized_filename = fs.normalize(fs.join(self._current_dir, filename))
+   local cache_filename = fs.join(self._dir, sha1.sha1(normalized_filename))
+
+   local file_mtime = fs.get_mtime(filename)
+   local cache_mtime = fs.get_mtime(cache_filename)
+
+   if not file_mtime or not cache_mtime or file_mtime >= cache_mtime then
+      return
+   end
+
+   local fh = io.open(cache_filename, "rb")
+
+   if not fh then
+      return
+   end
+
+   if fh:read() ~= format_version then
+      fh:close()
+      return
+   end
+
+   if fh:read() ~= normalized_filename then
+      fh:close()
+      return
+   end
+
+   local serialized_result = fh:read("*a")
+   fh:close()
+
+   if not serialized_result then
+      return nil, true
+   end
+
+   local result = serializer.load_check_result(serialized_result)
+
+   if not result then
+      return nil, true
+   end
+
+   return result
+end
+
+function cache.new(cache_directory)
+   return Cache(cache_directory)
+end
+
+return cache
diff --git a/script/luacheck/check.lua b/script/luacheck/check.lua
new file mode 100755
index 000000000..a995bf394
--- /dev/null
+++ b/script/luacheck/check.lua
@@ -0,0 +1,96 @@
+local check_state = require "luacheck.check_state"
+local core_utils = require "luacheck.core_utils"
+local parse_inline_options = require "luacheck.stages.parse_inline_options"
+local parser = require "luacheck.parser"
+local stages = require "luacheck.stages"
+local utils = require "luacheck.utils"
+
+local inline_option_fields = utils.array_to_set(parse_inline_options.inline_option_fields)
+
+local function validate_fields(tables, per_code_fields)
+   for _, t in ipairs(tables) do
+      local fields_set
+
+      if per_code_fields then
+         if not t.code then
+            error("Warning has no code", 0)
+         end
+
+         local warning_info = stages.warnings[t.code]
+
+         if not warning_info then
+            error("Unknown issue code " .. t.code, 0)
+         end
+
+         fields_set = warning_info.fields_set
+      else
+         fields_set = inline_option_fields
+      end
+
+      for field in pairs(t) do
+         if not fields_set[field] then
+            error("Unknown field " .. field .. " in " ..
+               (per_code_fields and "issue with code " .. t.code or "inline option table"), 0)
+         end
+      end
+   end
+end
+
+--- Checks source.
+-- Returns a table with results, with the following fields:
+--    `events`: array of issues and inline option events (options, push, or pop).
+--    `per_line_options`: map from line numbers to arrays of inline option events.
+--    `line_lengths`: map from line numbers to line lengths.
+--    `line_endings`: map from line numbers to "comment", "string", or `nil` base on
+--                    whether the line ending is within a token.
+-- If `events` array contains a syntax error, the other fields are empty tables.
+local function check(source)
+   local chstate = check_state.new(source)
+   local ok, error_wrapper = utils.try(stages.run, chstate)
+   local warnings, inline_options, line_lengths, line_endings
+
+   if ok then
+      warnings = chstate.warnings
+      core_utils.sort_by_location(warnings)
+      inline_options = chstate.inline_options
+      line_lengths = chstate.line_lengths
+      line_endings = chstate.line_endings
+   else
+      local err = error_wrapper.err
+
+      if not utils.is_instance(err, parser.SyntaxError) then
+         error(error_wrapper, 0)
+      end
+
+      local syntax_error = {
+         code = "011",
+         line = err.line,
+         column = chstate:offset_to_column(err.line, err.offset),
+         end_column = chstate:offset_to_column(err.line, err.end_offset),
+         msg = err.msg
+      }
+
+      if err.prev_line then
+         syntax_error.prev_line = err.prev_line
+         syntax_error.prev_column = chstate:offset_to_column(err.prev_line, err.prev_offset)
+         syntax_error.prev_end_column = chstate:offset_to_column(err.prev_line, err.prev_end_offset)
+      end
+
+      warnings = {syntax_error}
+      inline_options = {}
+      line_lengths = {}
+      line_endings = {}
+   end
+
+   validate_fields(warnings, true)
+   validate_fields(inline_options)
+
+   return {
+      warnings = warnings,
+      inline_options = inline_options,
+      line_lengths = line_lengths,
+      line_endings = line_endings
+   }
+end
+
+return check
diff --git a/script/luacheck/check_state.lua b/script/luacheck/check_state.lua
new file mode 100755
index 000000000..42cc0ac15
--- /dev/null
+++ b/script/luacheck/check_state.lua
@@ -0,0 +1,66 @@
+local utils = require "luacheck.utils"
+
+local check_state = {}
+
+local CheckState = utils.class()
+
+function CheckState:__init(source_bytes)
+   self.source_bytes = source_bytes
+   self.warnings = {}
+end
+
+-- Returns column of a character in a line given its offset.
+-- The column is never larger than the line length.
+-- This can be called if line length is not yet known.
+function CheckState:offset_to_column(line, offset)
+   local line_length = self.line_lengths[line]
+   local column = offset - self.line_offsets[line] + 1
+
+   if not line_length then
+      return column
+   end
+
+   return math.max(1, math.min(line_length, column))
+end
+
+function CheckState:warn_column_range(code, range, warning)
+   warning = warning or {}
+   warning.code = code
+   warning.line = range.line
+   warning.column = range.column
+   warning.end_column = range.end_column
+   table.insert(self.warnings, warning)
+   return warning
+end
+
+function CheckState:warn(code, line, offset, end_offset, warning)
+   warning = warning or {}
+   warning.code = code
+   warning.line = line
+   warning.column = self:offset_to_column(line, offset)
+   warning.end_column = self:offset_to_column(line, end_offset)
+   table.insert(self.warnings, warning)
+   return warning
+end
+
+function CheckState:warn_range(code, range, warning)
+   return self:warn(code, range.line, range.offset, range.end_offset, warning)
+end
+
+function CheckState:warn_var(code, var, warning)
+   warning = self:warn_range(code, var.node, warning)
+   warning.name = var.name
+   return warning
+end
+
+function CheckState:warn_value(code, value, warning)
+   warning = self:warn_range(code, value.var_node, warning)
+   warning.name = value.var.name
+   return warning
+end
+
+function check_state.new(source_bytes)
+   return CheckState(source_bytes)
+end
+
+return check_state
diff --git a/script/luacheck/config.lua b/script/luacheck/config.lua
new file mode 100755
index 000000000..d31143ce8
--- /dev/null
+++ b/script/luacheck/config.lua
@@ -0,0 +1,542 @@
+local cache = require "luacheck.cache"
+local options = require "luacheck.options"
+local builtin_standards = require "luacheck.builtin_standards"
+local fs = require "luacheck.fs"
+local globbing = require "luacheck.globbing"
+local standards = require "luacheck.standards"
+local utils = require "luacheck.utils"
+
+local config = {}
+
+local function get_global_config_dir()
+   if utils.is_windows then
+      local local_app_data_dir = os.getenv("LOCALAPPDATA")
+
+      if not local_app_data_dir then
+         local user_profile_dir = os.getenv("USERPROFILE")
+
+         if user_profile_dir then
+            local_app_data_dir = fs.join(user_profile_dir, "Local Settings", "Application Data")
+         end
+      end
+
+      if local_app_data_dir then
+         return fs.join(local_app_data_dir, "Luacheck")
+      end
+   else
+      local fh = assert(io.popen("uname -s"))
+      local system = fh:read("*l")
+      fh:close()
+
+      if system == "Darwin" then
+         local home_dir = os.getenv("HOME")
+
+         if home_dir then
+            return fs.join(home_dir, "Library", "Application Support", "Luacheck")
+         end
+      else
+         local config_home_dir = os.getenv("XDG_CONFIG_HOME")
+
+         if not config_home_dir then
+            local home_dir = os.getenv("HOME")
+
+            if home_dir then
+               config_home_dir = fs.join(home_dir, ".config")
+            end
+         end
+
+         if config_home_dir then
+            return fs.join(config_home_dir, "luacheck")
+         end
+      end
+   end
+end
+
+config.default_path = ".luacheckrc"
+
+function config.get_default_global_path()
+   local global_config_dir = get_global_config_dir()
+
+   if global_config_dir then
+      return fs.join(global_config_dir, config.default_path)
+   end
+end
+
+-- A single config is represented by a table with fields:
+-- * `options`: table with all config scope options, including `stds` and `files`.
+-- * `config_path`: optional path to file from which config was loaded, used only in error messages.
+-- * `anchor_dir`: absolute path to directory relative to which config was loaded,
+--   or nil if the config is not anchored. Paths within a config are adjusted to be absolute
+--   relative to anchor directory, or current directory if it's not anchored.
+--   As current directory can change between config usages, this adjustment happens on demand.
+
+-- Returns config path and optional anchor directory or nil and optional error message.
+local function locate_config(path, global_path)
+   if path == false then
+      return
+   end
+
+   local is_default_path = not path
+   path = path or config.default_path
+
+   if fs.is_absolute(path) then
+      return path
+   end
+
+   local current_dir = fs.get_current_dir()
+   local anchor_dir, rel_dir = fs.find_file(current_dir, path)
+
+   if anchor_dir then
+      return fs.join(rel_dir, path), anchor_dir
+   end
+
+   if not is_default_path then
+      return nil, ("Couldn't find configuration file %s"):format(path)
+   end
+
+   if global_path == false then
+      return
+   end
+
+   global_path = global_path or config.get_default_global_path()
+
+   if global_path and fs.is_file(global_path) then
+      return global_path, (fs.split_base(global_path))
+   end
+end
+
+local function try_load(path)
+   local src = utils.read_file(path)
+
+   if not src then
+      return
+   end
+
+   local func, err = utils.load(src, nil, "@"..path)
+   return err or func
+end
+
+local function add_relative_loader(anchor_dir)
+   if not anchor_dir then
+      return
+   end
+
+   local function loader(modname)
+      local modpath = fs.join(anchor_dir, (modname:gsub("%.", utils.dir_sep)))
+      return try_load(modpath..".lua") or try_load(modpath..utils.dir_sep.."init.lua"), modname
+   end
+
+   table.insert(package.loaders or package.searchers, 1, loader) -- luacheck: compat
+   return loader
+end
+
+local function remove_relative_loader(loader)
+   if not loader then
+      return
+   end
+
+   for i, func in ipairs(package.loaders or package.searchers) do -- luacheck: compat
+      if func == loader then
+         table.remove(package.loaders or package.searchers, i) -- luacheck: compat
+         return
+      end
+   end
+end
+
+-- Requires module from config anchor directory.
+-- Returns success flag and module or error message.
+function config.relative_require(anchor_dir, modname)
+   local loader = add_relative_loader(anchor_dir)
+   local ok, mod_or_err = pcall(require, modname)
+   remove_relative_loader(loader)
+   return ok, mod_or_err
+end
+
+-- Config must support special metatables for some keys:
+-- autovivification for `files`, fallback to built-in stds for `stds`.
+
+local special_mts = {
+   stds = {__index = builtin_standards},
+   files = {__index = function(files, key)
+      files[key] = {}
+      return files[key]
+   end}
+}
+
+local function make_config_env_mt()
+   local env_mt = {}
+   local special_values = {}
+
+   for key, mt in pairs(special_mts) do
+      special_values[key] = setmetatable({}, mt)
+   end
+
+   function env_mt.__index(_, key)
+      if special_mts[key] then
+         return special_values[key]
+      else
+         return _G[key]
+      end
+   end
+
+   function env_mt.__newindex(env, key, value)
+      if special_mts[key] then
+         if type(value) == "table" then
+            setmetatable(value, special_mts[key])
+         end
+
+         special_values[key] = value
+      else
+         rawset(env, key, value)
+      end
+   end
+
+   return env_mt, special_values
+end
+
+local function make_config_env()
+   local mt, special_values = make_config_env_mt()
+   return setmetatable({}, mt), special_values
+end
+
+local function remove_env_mt(env, special_values)
+   setmetatable(env, nil)
+   utils.update(env, special_values)
+end
+
+local function set_default_std(files, pattern, std)
+   -- Avoid mutating option tables, they may be shared between different patterns.
+   local pattern_opts = {std = std}
+
+   if files[pattern] then
+      pattern_opts = utils.update(pattern_opts, files[pattern])
+   end
+
+   files[pattern] = pattern_opts
+end
+
+local function add_default_path_options(opts)
+   local files = {}
+
+   if opts.files then
+      files = utils.update(files, opts.files)
+   end
+
+   opts.files = files
+   set_default_std(files, "**/spec/**/*_spec.lua", "+busted")
+   set_default_std(files, "**/test/**/*_spec.lua", "+busted")
+   set_default_std(files, "**/tests/**/*_spec.lua", "+busted")
+   set_default_std(files, "**/*.rockspec", "+rockspec")
+   set_default_std(files, "**/*.luacheckrc", "+luacheckrc")
+end
+
+local fallback_config = {options = {}, anchor_dir = ""}
+add_default_path_options(fallback_config.options)
+
+-- Loads config from a file, if possible.
+-- `path` and `global_path` can be nil (will use default), false (will disable loading), or a string.
+-- Doesn't validate the config.
+-- Returns a table or nil and an error message.
+function config.load_config(path, global_path)
+   local config_path, anchor_dir = locate_config(path, global_path)
+
+   if not config_path then
+      if anchor_dir then
+         return nil, anchor_dir
+      else
+         return fallback_config
+      end
+   end
+
+   local env, special_values = make_config_env()
+   local loader = add_relative_loader(anchor_dir)
+   local load_ok, ret, load_err = utils.load_config(config_path, env)
+   remove_relative_loader(loader)
+
+   if not load_ok then
+      return nil, ("Couldn't load configuration from %s: %s error (%s)"):format(config_path, ret, load_err)
+   end
+
+   -- Support returning some options from config instead of setting them as globals.
+   -- This allows easily loading options from another file, for example using require.
+   if type(ret) == "table" then
+      utils.update(env, ret)
+   end
+
+   remove_env_mt(env, special_values)
+   add_default_path_options(env)
+   return {options = env, config_path = config_path, anchor_dir = anchor_dir}
+end
+
+function config.table_to_config(opts)
+   return {options = opts}
+end
+
+-- Validates custom stds within a config table and adds them to stds map.
+-- Returns true on success or nil and an error message on error.
+local function add_stds_from_config(conf, stds)
+   if conf.options.stds ~= nil then
+      if type(conf.options.stds) ~= "table" then
+         return nil, ("invalid option 'stds': table expected, got %s"):format(type(conf.options.stds))
+      end
+
+      -- Validate stds in sorted order for deterministic output when more than one std is invalid.
+      local std_names = {}
+
+      for std_name in pairs(conf.options.stds) do
+         if type(std_name) == "string" then
+            table.insert(std_names, std_name)
+         end
+      end
+
+      table.sort(std_names)
+
+      for _, std_name in ipairs(std_names) do
+         local std = conf.options.stds[std_name]
+
+         if type(std) ~= "table" then
+            return nil, ("invalid custom std '%s': table expected, got %s"):format(std_name, type(std))
+         end
+
+         local ok, err = standards.validate_std_table(std)
+
+         if not ok then
+            return nil, ("invalid custom std '%s': %s"):format(std_name, err)
+         end
+
+         stds[std_name] = std
+      end
+   end
+
+   return true
+end
+
+local function error_prefix(conf)
+   if conf.config_path then
+      return ("in config loaded from %s: "):format(conf.config_path)
+   else
+      return ""
+   end
+end
+
+local function quiet_validator(x)
+   if type(x) == "number" then
+      if math.floor(x) == x and x >= 0 and x <= 3 then
+         return true
+      else
+         return false, ("integer in range 0..3 expected, got %.20g"):format(x)
+      end
+   else
+      return false, ("integer in range 0..3 expected, got %s"):format(type(x))
+   end
+end
+
+local function jobs_validator(x)
+   if type(x) == "number" then
+      if math.floor(x) == x and x >= 1 then
+         return true
+      else
+         return false, ("positive integer expected, got %.20g"):format(x)
+      end
+   else
+      return false, ("positive integer expected, got %s"):format(type(x))
+   end
+end
+
+config.format_options = {
+   quiet = quiet_validator,
+   color = utils.has_type("boolean"),
+   codes = utils.has_type("boolean"),
+   ranges = utils.has_type("boolean"),
+   formatter = utils.has_either_type("string", "function")
+}
+
+local top_options = {
+   cache = utils.has_either_type("string", "boolean"),
+   jobs = jobs_validator,
+   files = utils.has_type("table"),
+   stds = utils.has_type("table"),
+   exclude_files = utils.array_of("string"),
+   include_files = utils.array_of("string")
+}
+
+utils.update(top_options, config.format_options)
+utils.update(top_options, options.all_options)
+
+-- Returns true if config is valid, nil and error message otherwise.
+local function validate_config(conf, stds)
+   local ok, err = options.validate(top_options, conf.options, stds)
+
+   if not ok then
+      return nil, err
+   end
+
+   if conf.options.files then
+      for path, opts in pairs(conf.options.files) do
+         if type(path) == "string" then
+            ok, err = options.validate(options.all_options, opts, stds)
+
+            if not ok then
+               return nil, ("invalid options for path '%s': %s"):format(path, err)
+            end
+         end
+      end
+   end
+
+   return true
+end
+
+local ConfigStack = utils.class()
+
+function ConfigStack:__init(configs, stds)
+   self._configs = configs
+   self._stds = stds
+end
+
+function ConfigStack:get_stds()
+   return self._stds
+end
+
+-- Accepts an array of config tables, as returned from `load_config` and `table_to_config`.
+-- Assumes that configs closer to end of the array override configs closer to beginning.
+-- Returns an instance of `ConfigStack`. On validation error returns nil and an error message.
+function config.stack_configs(configs)
+   -- First, collect and validate stds from all configs, they are required to validate `std` option.
+   local stds = utils.update({}, builtin_standards)
+
+   for _, conf in ipairs(configs) do
+      local ok, err = add_stds_from_config(conf, stds)
+
+      if not ok then
+         return nil, error_prefix(conf) .. err
+      end
+   end
+
+   for _, conf in ipairs(configs) do
+      local ok, err = validate_config(conf, stds)
+
+      if not ok then
+         return nil, error_prefix(conf) .. err
+      end
+   end
+
+   return ConfigStack(configs, stds)
+end
+
+-- Returns a table of top-level config options, except `files` and `stds`.
+function ConfigStack:get_top_options()
+   local res = {
+      quiet = 0,
+      color = true,
+      codes = false,
+      ranges = false,
+      formatter = "default",
+      cache = false,
+      jobs = false,
+      include_files = {},
+      exclude_files = {}
+   }
+
+   local current_dir = fs.get_current_dir()
+   local last_anchor_dir
+
+   for _, conf in ipairs(self._configs) do
+      for _, option in ipairs({"quiet", "color", "codes", "ranges", "jobs"}) do
+         if conf.options[option] ~= nil then
+            res[option] = conf.options[option]
+         end
+      end
+
+      -- It's not immediately obvious relatively to which config formatter modules
+      -- should be resolved when they are specified in a config without an anchor dir.
+      -- For now, use the last anchor directory available, that should result
+      -- in reasonable behaviour in the current case of a single anchored config (loaded from file)
+      -- + a single not anchored config (loaded from CLI options).
+      last_anchor_dir = conf.anchor_dir or last_anchor_dir
+
+      if conf.options.formatter ~= nil then
+         res.formatter = conf.options.formatter
+         res.formatter_anchor_dir = last_anchor_dir
+      end
+
+      -- Path options, on the other hand, are interpreted relatively to the current directory
+      -- when specified in a config without anchor. Behaviour similar to formatter could also
+      -- make sense, but this is consistent with pre 0.22.0 behaviou
+      local anchor_dir = conf.anchor_dir or current_dir
+
+      for _, option in ipairs({"include_files", "exclude_files"}) do
+         if conf.options[option] ~= nil then
+            for _, glob in ipairs(conf.options[option]) do
+               table.insert(res[option], fs.normalize(fs.join(anchor_dir, glob)))
+            end
+         end
+      end
+
+      if conf.options.cache ~= nil then
+         if conf.options.cache == true then
+            if not res.cache then
+               res.cache = fs.normalize(fs.join(last_anchor_dir or current_dir, cache.get_default_dir()))
+            end
+         elseif conf.options.cache == false then
+            res.cache = false
+         else
+            res.cache = fs.normalize(fs.join(anchor_dir, conf.options.cache))
+         end
+      end
+   end
+
+   return res
+end
+
+local function add_applying_overrides(option_stack, conf, filename)
+   if not filename or not conf.options.files then
+      return
+   end
+
+   local current_dir = fs.get_current_dir()
+   local abs_filename = fs.normalize(fs.join(current_dir, filename))
+   local anchor_dir
+
+   if conf.anchor_dir == "" then
+      anchor_dir = fs.split_base(current_dir)
+   else
+      anchor_dir = conf.anchor_dir or current_dir
+   end
+
+   local matching_pairs = {}
+
+   for glob, opts in pairs(conf.options.files) do
+      if type(glob) == "string" then
+         local abs_glob = fs.normalize(fs.join(anchor_dir, glob))
+
+         if globbing.match(abs_glob, abs_filename) then
+            table.insert(matching_pairs, {
+               abs_glob = abs_glob,
+               opts = opts
+            })
+         end
+      end
+   end
+
+   table.sort(matching_pairs, function(pair1, pair2)
+      return globbing.compare(pair1.abs_glob, pair2.abs_glob)
+   end)
+
+   for _, pair in ipairs(matching_pairs) do
+      table.insert(option_stack, pair.opts)
+   end
+end
+
+-- Returns an option stack applicable to a file with given name, or in general if name is not given.
+function ConfigStack:get_options(filename)
+   local res = {}
+
+   for _, conf in ipairs(self._configs) do
+      table.insert(res, conf.options)
+      add_applying_overrides(res, conf, filename)
+   end
+
+   return res
+end
+
+return config
diff --git a/script/luacheck/core_utils.lua b/script/luacheck/core_utils.lua
new file mode 100755
index 000000000..a07892265
--- /dev/null
+++ b/script/luacheck/core_utils.lua
@@ -0,0 +1,95 @@
+local decoder = require "luacheck.decoder"
+local utils = require "luacheck.utils"
+
+local core_utils = {}
+
+-- Attempts to evaluate a node as a Lua value, without resolving locals.
+-- Returns Lua value and its string representation on success, nothing on failure.
+function core_utils.eval_const_node(node)
+   if node.tag == "True" then
+      return true, "true"
+   elseif node.tag == "False" then
+      return false, "false"
+   elseif node.tag == "String" then
+      local chars = decoder.decode(node[1])
+      return node[1], chars:get_printable_substring(1, chars:get_length())
+   else
+      local is_negative
+
+      if node.tag == "Op" and node[1] == "unm" then
+         is_negative = true
+         node = node[2]
+      end
+
+      if node.tag ~= "Number" then
+         return
+      end
+
+      local str = node[1]
+
+      if str:find("[iIuUlL]") then
+         -- Ignore LuaJIT cdata literals.
+         return
+      end
+
+      -- On Lua 5.3 convert to float to get same results as on Lua 5.1 and 5.2.
+      if _VERSION == "Lua 5.3" and not str:find("[%.eEpP]") then
+         str = str .. ".0"
+      end
+
+      local number = tonumber(str)
+
+      if not number then
+         return
+      end
+
+      if is_negative then
+         number = -number
+      end
+
+      if number == number and number < 1/0 and number > -1/0 then
+         return number, (is_negative and "-" or "") .. node[1]
+      end
+   end
+end
+
+local statement_containing_tags = utils.array_to_set({"Do", "While", "Repeat", "Fornum", "Forin", "If"})
+
+-- `items` is an array of nodes or nested item arrays.
+local function scan_for_statements(chstate, items, tags, callback, ...)
+   for _, item in ipairs(items) do
+      if tags[item.tag] then
+         callback(chstate, item, ...)
+      end
+
+      if not item.tag or statement_containing_tags[item.tag] then
+         scan_for_statements(chstate, item, tags, callback, ...)
+      end
+   end
+end
+
+-- Calls `callback(chstate, node, ...)` for each statement node within AST with tag in given array.
+function core_utils.each_statement(chstate, tags_array, callback, ...)
+   local tags = utils.array_to_set(tags_array)
+
+   for _, line in ipairs(chstate.lines) do
+      scan_for_statements(chstate, line.node[2], tags, callback, ...)
+   end
+end
+
+local function location_comparator(warning1, warning2)
+   if warning1.line ~= warning2.line then
+      return warning1.line < warning2.line
+   elseif warning1.column ~= warning2.column then
+      return warning1.column < warning2.column
+   else
+      return warning1.code < warning2.code
+   end
+end
+
+-- Sorts an array of warnings by location information as provided in `line` and `column` fields.
+function core_utils.sort_by_location(warnings)
+   table.sort(warnings, location_comparator)
+end
+
+return core_utils
diff --git a/script/luacheck/decoder.lua b/script/luacheck/decoder.lua
new file mode 100755
index 000000000..e2d36636c
--- /dev/null
+++ b/script/luacheck/decoder.lua
@@ -0,0 +1,199 @@
+local unicode = require "luacheck.unicode"
+local utils = require "luacheck.utils"
+
+local decoder = {}
+
+local sbyte = string.byte
+local sfind = string.find
+local sgsub = string.gsub
+local ssub = string.sub
+
+-- `LatinChars` and `UnicodeChars` objects represent source strings
+-- and provide Unicode-aware access to them with a common interface.
+-- Source bytes should not be accessed directly.
+-- Provided methods are:
+-- `Chars:get_codepoint(index)`: returns codepoint at given index as integer or nil if index is out of range.
+-- `Chars:get_substring(from, to)`: returns substring of original bytes corresponding to characters from `from` to `to`.
+-- `Chars:get_printable_substring(from. to)`: like get_substring but escapes not printable characters.
+-- `Chars:get_length()`: returns total number of characters.
+-- `Chars:find(pattern, from)`: `string.find` but `from` is in characters. Return values are still in bytes.
+
+-- `LatinChars` is an optimized special case for latin1 strings.
+local LatinChars = utils.class()
+
+function LatinChars:__init(bytes)
+   self._bytes = bytes
+end
+
+function LatinChars:get_codepoint(index)
+   return sbyte(self._bytes, index)
+end
+
+function LatinChars:get_substring(from, to)
+   return ssub(self._bytes, from, to)
+end
+
+local function hexadecimal_escaper(byte)
+   return ("\\x%02X"):format(sbyte(byte))
+end
+
+function LatinChars:get_printable_substring(from, to)
+   return (sgsub(ssub(self._bytes, from, to), "[^\32-\126]", hexadecimal_escaper))
+end
+
+function LatinChars:get_length()
+   return #self._bytes
+end
+
+function LatinChars:find(pattern, from)
+   return sfind(self._bytes, pattern, from)
+end
+
+-- Decodes `bytes` as UTF8. Returns arrays of codepoints as integers and their byte offsets.
+-- Byte offsets have one extra item pointing to one byte past the end of `bytes`.
+-- On decoding error returns nothing.
+local function get_codepoints_and_byte_offsets(bytes)
+   local codepoints = {}
+   local byte_offsets = {}
+
+   local byte_index = 1
+   local codepoint_index = 1
+
+   while true do
+      byte_offsets[codepoint_index] = byte_index
+
+      -- Attempt to decode the next codepoint from UTF8.
+      local codepoint = sbyte(bytes, byte_index)
+
+      if not codepoint then
+         return codepoints, byte_offsets
+      end
+
+      byte_index = byte_index + 1
+
+      if codepoint >= 0x80 then
+         -- Not ASCII.
+
+         if codepoint < 0xC0 then
+            return
+         end
+
+         local cont = (sbyte(bytes, byte_index) or 0) - 0x80
+
+         if cont < 0 or cont >= 0x40 then
+            return
+         end
+
+         byte_index = byte_index + 1
+
+         if codepoint < 0xE0 then
+            -- Two bytes.
+            codepoint = cont + (codepoint - 0xC0) * 0x40
+         elseif codepoint < 0xF0 then
+            -- Three bytes.
+            codepoint = cont + (codepoint - 0xE0) * 0x40
+
+            cont = (sbyte(bytes, byte_index) or 0) - 0x80
+
+            if cont < 0 or cont >= 0x40 then
+               return
+            end
+
+            byte_index = byte_index + 1
+
+            codepoint = cont + codepoint * 0x40
+         elseif codepoint < 0xF8 then
+            -- Four bytes.
+            codepoint = cont + (codepoint - 0xF0) * 0x40
+
+            cont = (sbyte(bytes, byte_index) or 0) - 0x80
+
+            if cont < 0 or cont >= 0x40 then
+               return
+            end
+
+            byte_index = byte_index + 1
+
+            codepoint = cont + codepoint * 0x40
+
+            cont = (sbyte(bytes, byte_index) or 0) - 0x80
+
+            if cont < 0 or cont >= 0x40 then
+               return
+            end
+
+            byte_index = byte_index + 1
+
+            codepoint = cont + codepoint * 0x40
+
+            if codepoint > 0x10FFFF then
+               return
+            end
+         else
+            return
+         end
+      end
+
+      codepoints[codepoint_index] = codepoint
+      codepoint_index = codepoint_index + 1
+   end
+end
+
+-- `UnicodeChars` is the general case for non-latin1 strings.
+-- Assumes UTF8, on decoding error falls back to latin1.
+local UnicodeChars = utils.class()
+
+function UnicodeChars:__init(bytes, codepoints, byte_offsets)
+   self._bytes = bytes
+   self._codepoints = codepoints
+   self._byte_offsets = byte_offsets
+end
+
+function UnicodeChars:get_codepoint(index)
+   return self._codepoints[index]
+end
+
+function UnicodeChars:get_substring(from, to)
+   local byte_offsets = self._byte_offsets
+   return ssub(self._bytes, byte_offsets[from], byte_offsets[to + 1] - 1)
+end
+
+function UnicodeChars:get_printable_substring(from, to)
+   -- This is only called on syntax error, it's okay to be slow.
+   local parts = {}
+
+   for index = from, to do
+      local codepoint = self._codepoints[index]
+
+      if unicode.is_printable(codepoint) then
+         table.insert(parts, self:get_substring(index, index))
+      else
+         table.insert(parts, (codepoint > 255 and "\\u{%X}" or "\\x%02X"):format(codepoint))
+      end
+   end
+
+   return table.concat(parts)
+end
+
+function UnicodeChars:get_length()
+   return #self._codepoints
+end
+
+function UnicodeChars:find(pattern, from)
+   return sfind(self._bytes, pattern, self._byte_offsets[from])
+end
+
+function decoder.decode(bytes)
+   -- Only use UnicodeChars if necessary. LatinChars isn't much faster but noticeably more memory efficient.
+   if sfind(bytes, "[\128-\255]") then
+      local codepoints, byte_offsets = get_codepoints_and_byte_offsets(bytes)
+
+      if codepoints then
+         return UnicodeChars(bytes, codepoints, byte_offsets)
+      end
+   end
+
+   return LatinChars(bytes)
+end
+
+return decoder
diff --git a/script/luacheck/expand_rockspec.lua b/script/luacheck/expand_rockspec.lua
new file mode 100755
index 000000000..7737daa43
--- /dev/null
+++ b/script/luacheck/expand_rockspec.lua
@@ -0,0 +1,110 @@
+local fs = require "luacheck.fs"
+local utils = require "luacheck.utils"
+
+local blacklist = utils.array_to_set({"spec", ".luarocks", "lua_modules", "test.lua", "tests.lua"})
+
+-- This reimplements relevant parts of `luarocks.build.builtin.autodetect_modules`.
+-- Autodetection works relatively to the directory containing the rockspec.
+local function autodetect_modules(rockspec_path)
+   rockspec_path = fs.normalize(rockspec_path)
+   local base, rest = fs.split_base(rockspec_path)
+   local project_dir = base .. (rest:match("^(.*)" .. utils.dir_sep .. ".*$") or "")
+
+   if project_dir == "" then
+      project_dir = "."
+   end
+
+   local module_dir = project_dir
+
+   for _, module_subdir in ipairs({"src", "lua", "lib"}) do
+      local full_module_dir = fs.join(project_dir, module_subdir)
+
+      if fs.is_dir(full_module_dir) then
+         module_dir = full_module_dir
+         break
+      end
+   end
+
+   local res = {}
+
+   for _, file in ipairs((fs.extract_files(module_dir, "%.lua$"))) do
+      -- Extract first part of the path from module_dir to the file, or file name itself.
+      if not blacklist[file:match("^" .. module_dir:gsub("%p", "%%%0") .. "[\\/]*([^\\/]*)")] then
+         table.insert(res, file)
+      end
+   end
+
+   local bin_dir
+
+   for _, bin_subdir in ipairs({"src/bin", "bin"}) do
+      local full_bin_dir = fs.join(project_dir, bin_subdir)
+
+      if fs.is_dir(full_bin_dir) then
+         bin_dir = full_bin_dir
+      end
+   end
+
+   if bin_dir then
+      local iter, state, var = fs.dir_iter(bin_dir)
+
+      if iter then
+         for basename in iter, state, var do
+            if basename:sub(-#".lua") == ".lua" then
+               table.insert(res, fs.join(bin_dir, basename))
+            end
+         end
+      end
+   end
+
+   return res
+end
+
+local function extract_lua_files(rockspec_path, rockspec)
+   local build = rockspec.build
+
+   if type(build) ~= "table" then
+      return autodetect_modules(rockspec_path)
+   end
+
+   if not build.type or build.type == "builtin" or build.type == "module" then
+      if not build.modules then
+         return autodetect_modules(rockspec_path)
+      end
+   end
+
+   local res = {}
+
+   local function scan(t)
+      if type(t) == "table" then
+         for _, file in pairs(t) do
+            if type(file) == "string" and file:sub(-#".lua") == ".lua" then
+               table.insert(res, file)
+            end
+         end
+      end
+   end
+
+   scan(build.modules)
+
+   if type(build.install) == "table" then
+      scan(build.install.lua)
+      scan(build.install.bin)
+   end
+
+   table.sort(res)
+   return res
+end
+
+-- Receives a name of a rockspec, returns list of related .lua files.
+-- On error returns nil and "I/O", "syntax", or "runtime" and error message.
+local function expand_rockspec(rockspec_path)
+   local rockspec, err_type, err_msg = utils.load_config(rockspec_path)
+
+   if not rockspec then
+      return nil, err_type, err_msg
+   end
+
+   return extract_lua_files(rockspec_path, rockspec)
+end
+
+return expand_rockspec
diff --git a/script/luacheck/filter.lua b/script/luacheck/filter.lua
new file mode 100755
index 000000000..5ef8fd1bc
--- /dev/null
+++ b/script/luacheck/filter.lua
@@ -0,0 +1,535 @@
+local core_utils = require "luacheck.core_utils"
+local decoder = require "luacheck.decoder"
+local options = require "luacheck.options"
+local utils = require "luacheck.utils"
+
+local filter = {}
+
+-- Returns two optional booleans indicating if warning matches pattern by code and name.
+local function match(pattern, code, name)
+   local matches_code, matches_name
+   local code_pattern, name_pattern = pattern[1], pattern[2]
+
+   if code_pattern then
+      matches_code = utils.pmatch(code, code_pattern)
+   end
+
+   if name_pattern then
+      if not name then
+         -- Warnings without name field can't match by name.
+         matches_name = false
+      else
+         matches_name = utils.pmatch(name, name_pattern)
+      end
+   end
+
+   return matches_code, matches_name
+end
+
+local function passes_rules_filter(rules, code, name)
+   -- A warning is enabled when its code and name are enabled.
+   local enabled_code, enabled_name = false, false
+
+   for _, rule in ipairs(rules) do
+      local matches_one = false
+
+      for _, pattern in ipairs(rule[1]) do
+         local matches_code, matches_name = match(pattern, code, name)
+
+         -- If a factor is enabled, warning can't be disabled by it.
+         if enabled_code then
+            matches_code = rule[2] ~= "disable"
+         end
+
+         if enabled_name then
+            matches_code = rule[2] ~= "disable"
+         end
+
+         if (matches_code and matches_name ~= false) or
+               (matches_name and matches_code ~= false) then
+            matches_one = true
+         end
+
+         if rule[2] == "enable" then
+            if matches_code then
+               enabled_code = true
+            end
+
+            if matches_name then
+               enabled_name = true
+            end
+
+            if enabled_code and enabled_name then
+               -- Enable as matching to some `enable` pattern by code and to another by name.
+               return true
+            end
+         elseif rule[2] == "disable" then
+            if matches_one then
+               -- Disable as matching to `disable` pattern.
+               return false
+            end
+         end
+      end
+
+      if rule[2] == "only" and not matches_one then
+         -- Disable as not matching to any of `only` patterns.
+         return false
+      end
+   end
+
+   -- Enable by default.
+   return true
+end
+
+local function get_field_string(warning)
+   local parts = {}
+
+   if warning.indexing then
+      for _, index in ipairs(warning.indexing) do
+         local part
+
+         if type(index) == "string" then
+            local chars = decoder.decode(index)
+            part = chars:get_printable_substring(1, chars:get_length())
+         else
+            part = "?"
+         end
+
+         table.insert(parts, part)
+      end
+   end
+
+   return table.concat(parts, ".")
+end
+
+local function get_field_status(opts, warning, depth)
+   local def = opts.std
+   local defined = true
+   local read_only = true
+
+   for i = 1, depth or (warning.indexing and #warning.indexing or 0) + 1 do
+      local index_string = i == 1 and warning.name or warning.indexing[i - 1]
+
+      if index_string == true then
+         -- Indexing with something that may or may not be a string.
+         if (def.fields and next(def.fields)) or def.other_fields then
+            if def.deep_read_only then
+               read_only = true
+            else
+               read_only = false
+            end
+         else
+            defined = false
+         end
+
+         break
+      elseif index_string == false then
+         -- Indexing with not a string.
+         if not def.other_fields then
+            defined = false
+         end
+
+         break
+      else
+         -- Indexing with a constant string.
+         if def.fields and def.fields[index_string] then
+            -- The field is defined, recurse into it.
+            def = def.fields[index_string]
+
+            if def.read_only ~= nil then
+               read_only = def.read_only
+            end
+         else
+            -- The field is not defined, but it may be okay to index if `other_fields` is true.
+            if not def.other_fields then
+               defined = false
+            end
+
+            break
+         end
+      end
+   end
+
+   return defined and (read_only and "read_only" or "global") or "undefined"
+end
+
+-- Checks if a warning passes options filter. May add some fields required for formatting.
+local function passes_filter(normalized_options, warning)
+   if warning.code == "561" then
+      local max_complexity = normalized_options.max_cyclomatic_complexity
+
+      if not max_complexity or warning.complexity <= max_complexity then
+         return false
+      end
+
+      warning.max_complexity = max_complexity
+   elseif warning.code:find("^[234]") and warning.name == "_" and not warning.useless then
+      return false
+   elseif warning.code:find("^1[14]") then
+      if warning.indirect and
+            get_field_status(normalized_options, warning, warning.previous_indexing_len) == "undefined" then
+         return false
+      end
+
+      if not warning.module and get_field_status(normalized_options, warning) ~= "undefined" then
+         return false
+      end
+   end
+
+   if warning.code:find("^1[24][23]") then
+      warning.field = get_field_string(warning)
+   end
+
+   if warning.secondary and not normalized_options.unused_secondaries then
+      return false
+   end
+
+   if warning.self and not normalized_options.self then
+      return false
+   end
+
+   return passes_rules_filter(normalized_options.rules, warning.code, warning.name)
+end
+
+local empty_options = {}
+
+-- Updates option_stack for given line with next_index pointing to the inline option past the previous line.
+-- Adds warnings for invalid inline options to check_result, filtered_warnings.
+-- Returns updated next_index.
+local function update_option_stack_for_new_line(check_result, stds, option_stack, line, next_index)
+   local inline_option = check_result.inline_options[next_index]
+
+   if not inline_option or inline_option.line > line then
+      -- No inline options on this line, option stack for the line is ready.
+      return next_index
+   end
+
+   next_index = next_index + 1
+
+   if inline_option.pop_count then
+      for _ = 1, inline_option.pop_count do
+         table.remove(option_stack)
+      end
+   end
+
+   if not inline_option.options then
+      -- No inline option push on this line, option stack for the line is ready.
+      return next_index
+   end
+
+   local options_ok, err_msg = options.validate(options.all_options, inline_option.options, stds)
+
+   if not options_ok then
+      -- Warn about invalid inline option, push a dummy empty table instead to keep pop counts correct.
+      inline_option.options = nil
+      inline_option.code = "021"
+      inline_option.msg = err_msg
+      table.insert(check_result.filtered_warnings, inline_option)
+
+      -- Reuse empty table identity so that normalized option caching works better.
+      table.insert(option_stack, empty_options)
+   else
+      table.insert(option_stack, inline_option.options)
+   end
+
+   return next_index
+end
+
+-- Warns (adds to check_result.filtered_warnings) about a line if it's too long
+-- and the warning is not filtered out by options.
+local function check_line_length(check_result, normalized_options, line)
+   local line_length = check_result.line_lengths[line]
+   local line_type = check_result.line_endings[line]
+   local max_length = normalized_options["max_" .. (line_type or "code") .. "_line_length"]
+
+   if max_length and line_length > max_length then
+      if passes_rules_filter(normalized_options.rules, "631") then
+         table.insert(check_result.filtered_warnings, {
+            code = "631",
+            line = line,
+            column = max_length + 1,
+            end_column = line_length,
+            max_length = max_length,
+            line_ending = line_type
+         })
+      end
+   end
+end
+
+-- Adds warnings passing filtering and not related to globals to check_result.filtered_warnings.
+-- If there is a global related warning on this line, sets check_results[line] to normalized_optuons.
+local function filter_warnings_on_new_line(check_result, normalized_options, line, next_index)
+   while true do
+      local warning = check_result.warnings[next_index]
+
+      if not warning or warning.line > line then
+         -- No more warnings on this line.
+         break
+      end
+
+      if warning.code:find("^1") then
+         check_result.normalized_options[line] = normalized_options
+      elseif passes_filter(normalized_options, warning) then
+         table.insert(check_result.filtered_warnings, warning)
+      end
+
+      next_index = next_index + 1
+   end
+
+   return next_index
+end
+
+-- Normalizing options is relatively expensive because full std definitions are quite large.
+-- `CachingOptionsNormalizer` implements a caching layer that reduces number of `options.normalize` calls.
+-- Caching is done based on identities of option tables.
+
+local CachingOptionsNormalizer = utils.class()
+
+function CachingOptionsNormalizer:__init()
+   self.result_trie = {}
+end
+
+function CachingOptionsNormalizer:normalize_options(stds, option_stack)
+   local result_node = self.result_trie
+
+   for _, option_table in ipairs(option_stack) do
+      if not result_node[option_table] then
+         result_node[option_table] = {}
+      end
+
+      result_node = result_node[option_table]
+   end
+
+   if result_node.result then
+      return result_node.result
+   end
+
+   local result = options.normalize(option_stack, stds)
+   result_node.result = result
+   return result
+end
+
+-- May mutate base_opts_stack.
+local function filter_not_global_related_in_file(check_result, options_normalizer, stds, option_stack)
+   check_result.filtered_warnings = {}
+   check_result.normalized_options = {}
+
+   -- Iterate over lines, warnings, and inline options at the same time, keeping opts_stack up to date.
+   local next_warning_index = 1
+   local next_inline_option_index = 1
+
+   for line in ipairs(check_result.line_lengths) do
+      next_inline_option_index = update_option_stack_for_new_line(
+         check_result, stds, option_stack, line, next_inline_option_index)
+      local normalized_options = options_normalizer:normalize_options(stds, option_stack)
+      check_line_length(check_result, normalized_options, line)
+      next_warning_index = filter_warnings_on_new_line(check_result, normalized_options, line, next_warning_index)
+   end
+end
+
+local function may_have_options(opts_table)
+   for key in pairs(opts_table) do
+      if type(key) == "string" then
+         return true
+      end
+   end
+
+   return false
+end
+
+local function get_option_stack(opts, file_index)
+   local res = {opts}
+
+   if opts and opts[file_index] then
+      -- Don't add useless per-file option tables, that messes up normalized option caching
+      -- since it memorizes based on option table identities.
+      if may_have_options(opts[file_index]) then
+         table.insert(res, opts[file_index])
+      end
+
+      for _, nested_opts in ipairs(opts[file_index]) do
+         table.insert(res, nested_opts)
+      end
+   end
+
+   return res
+end
+
+-- For each file check result:
+-- * Stores invalid inline options, not filtered out not global-related warnings, and newly created line length warnings
+--   in .filtered_warnings.
+-- * Stores a map from line numbers to normalized options for lines of global-related warnings in .normalized_options.
+local function filter_not_global_related(check_results, opts, stds)
+   local caching_options_normalizer = CachingOptionsNormalizer()
+
+   for file_index, check_result in ipairs(check_results) do
+      if not check_result.fatal then
+         if check_result.warnings[1] and check_result.warnings[1].code == "011" then
+            -- Special case syntax errors, they don't have line numbers so normal filtering does not work.
+            check_result.filtered_warnings = check_result.warnings
+            check_result.normalized_options = {}
+         else
+            local base_file_option_stack = get_option_stack(opts, file_index)
+            filter_not_global_related_in_file(check_result, caching_options_normalizer, stds, base_file_option_stack)
+         end
+      end
+   end
+end
+
+-- A global is implicitly defined in a file if opts.allow_defined == true and it is set anywhere in the file,
+--    or opts.allow_defined_top == true and it is set in the top level function scope.
+-- By default, accessing and setting globals in a file is allowed for explicitly defined globals (standard and custom)
+--    for that file and implicitly defined globals from that file and
+--    all other files except modules (files with opts.module == true).
+-- Accessing other globals results in "accessing undefined variable" warning.
+-- Setting other globals results in "setting non-standard global variable" warning.
+-- Unused implicitly defined global results in "unused global variable" warning.
+-- For modules, accessing globals uses same rules as normal files, however,
+--    setting globals is only allowed for implicitly defined globals from the module.
+-- Setting a global not defined in the module results in "setting non-module global variable" warning.
+
+local function is_definition(normalized_options, warning)
+   return normalized_options.allow_defined or (normalized_options.allow_defined_top and warning.top)
+end
+
+-- Extracts sets of defined, exported and used globals from a file check result.
+local function get_implicit_globals_in_file(check_result)
+   local defined = {}
+   local exported = {}
+   local used = {}
+
+   for _, warning in ipairs(check_result.warnings) do
+      if warning.code:find("^11") then
+         if warning.code == "111" then
+            local normalized_options = check_result.normalized_options[warning.line]
+
+            if is_definition(normalized_options, warning) then
+               if normalized_options.module then
+                  defined[warning.name] = true
+               else
+                  exported[warning.name] = true
+               end
+            end
+         else
+            used[warning.name] = true
+         end
+      end
+   end
+
+   return defined, exported, used
+end
+
+-- Returns set of globals defines across all files except modules, a set of globals used across all files,
+-- and an array of sets of globals defined per file, parallel to the check results array.
+local function get_implicit_globals(check_results)
+   local globally_defined = {}
+   local globally_used = {}
+   local locally_defined = {}
+
+   for file_index, check_result in ipairs(check_results) do
+      if not check_result.fatal then
+         local defined, exported, used = get_implicit_globals_in_file(check_result)
+         utils.update(globally_defined, exported)
+         utils.update(globally_used, used)
+         locally_defined[file_index] = defined
+      end
+   end
+
+   return globally_defined, globally_used, locally_defined
+end
+
+-- Mutates the warning and returns it or discards it by returning nothing if it's filtered out.
+local function apply_implicit_definitions(globally_defined, globally_used, locally_defined, normalized_options, warning)
+   if not warning.code:find("^11") then
+      return warning
+   end
+
+   if warning.code == "111" then
+      if normalized_options.module then
+         if locally_defined[warning.name] then
+            return
+         end
+
+         warning.module = true
+      else
+         if is_definition(normalized_options, warning) then
+            if globally_used[warning.name] then
+               return
+            end
+
+            warning.code = "131"
+            warning.top = nil
+         else
+            if globally_defined[warning.name] then
+               return
+            end
+         end
+      end
+   else
+      if globally_defined[warning.name] or locally_defined[warning.name] then
+         return
+      end
+   end
+
+   return warning
+end
+
+local function filter_global_related_in_file(check_result, globally_defined, globally_used, locally_defined)
+   for _, warning in ipairs(check_result.warnings) do
+      if warning.code:find("^1") then
+         local normalized_options = check_result.normalized_options[warning.line]
+         warning = apply_implicit_definitions(
+            globally_defined, globally_used, locally_defined, normalized_options, warning)
+
+         if warning then
+            if warning.code:find("^11[12]") and not warning.module and
+                  get_field_status(normalized_options, warning) == "read_only" then
+               warning.code = "12" .. warning.code:sub(3, 3)
+            elseif warning.code:find("^11[23]") and get_field_status(normalized_options, warning, 1) ~= "undefined" then
+               warning.code = "14" .. warning.code:sub(3, 3)
+            end
+
+            if warning.code:match("11[23]") and get_field_status(normalized_options, warning, 1) ~= "undefined" then
+               warning.code = "14" .. warning.code:sub(3, 3)
+            end
+
+            if passes_filter(normalized_options, warning) then
+               table.insert(check_result.filtered_warnings, warning)
+            end
+         end
+      end
+   end
+end
+
+local function filter_global_related(check_results)
+   local globally_defined, globally_used, locally_defined = get_implicit_globals(check_results)
+
+   for file_index, check_result in ipairs(check_results) do
+      if not check_result.fatal then
+         filter_global_related_in_file(check_result, globally_defined, globally_used, locally_defined[file_index])
+      end
+   end
+end
+
+-- Processes an array of results of the check stage (or tables with .fatal field) into the final report.
+-- `opts[i]`, if present, is used as options when processing `report[i]` together with options in its array part.
+-- This function may mutate check results or reuse its parts in the return value.
+function filter.filter(check_results, opts, stds)
+   filter_not_global_related(check_results, opts, stds)
+   filter_global_related(check_results)
+
+   local report = {}
+
+   for file_index, check_result in ipairs(check_results) do
+      if check_result.fatal then
+         report[file_index] = check_result
+      else
+         core_utils.sort_by_location(check_result.filtered_warnings)
+         report[file_index] = check_result.filtered_warnings
+      end
+   end
+
+   return report
+end
+
+return filter
diff --git a/script/luacheck/format.lua b/script/luacheck/format.lua
new file mode 100755
index 000000000..96f39a5fd
--- /dev/null
+++ b/script/luacheck/format.lua
@@ -0,0 +1,334 @@
+local stages = require "luacheck.stages"
+local utils = require "luacheck.utils"
+
+local format = {}
+
+local color_support = not utils.is_windows or os.getenv("ANSICON")
+
+local function get_message_format(warning)
+   local message_format = assert(stages.warnings[warning.code], "Unkown warning code " .. warning.code).message_format
+
+   if type(message_format) == "function" then
+      return message_format(warning)
+   else
+      return message_format
+   end
+end
+
+local function plural(number)
+   return (number == 1) and "" or "s"
+end
+
+local color_codes = {
+   reset = 0,
+   bright = 1,
+   red = 31,
+   green = 32
+}
+
+local function encode_color(c)
+   return "\27[" .. tostring(color_codes[c]) .. "m"
+end
+
+local function colorize(str, ...)
+   str = str .. encode_color("reset")
+
+   for _, color in ipairs({...}) do
+      str = encode_color(color) .. str
+   end
+
+   return encode_color("reset") .. str
+end
+
+local function format_color(str, color, ...)
+   return color and colorize(str, ...) or str
+end
+
+local function format_number(number, color)
+   return format_color(tostring(number), color, "bright", (number > 0) and "red" or "reset")
+end
+
+-- Substitutes markers within string format with values from a table.
+-- "{field_name}" marker is replaced with `values.field_name`.
+-- "{field_name!}" marker adds highlight or quoting depending on color
+-- option.
+local function substitute(string_format, values, color)
+   return (string_format:gsub("{([_a-zA-Z0-9]+)(!?)}", function(field_name, highlight)
+      local value = tostring(assert(values[field_name], "No field " .. field_name))
+
+      if highlight == "!" then
+         if color then
+            return colorize(value, "bright")
+         else
+            return "'" .. value .. "'"
+         end
+      else
+         return value
+      end
+   end))
+end
+
+local function format_message(event, color)
+   return substitute(get_message_format(event), event, color)
+end
+
+-- Returns formatted message for an issue, without color.
+function format.get_message(event)
+   return format_message(event)
+end
+
+local function capitalize(str)
+   return str:gsub("^.", string.upper)
+end
+
+local function fatal_type(file_report)
+   return capitalize(file_report.fatal) .. " error"
+end
+
+local function count_warnings_errors(events)
+   local warnings, errors = 0, 0
+
+   for _, event in ipairs(events) do
+      if event.code:sub(1, 1) == "0" then
+         errors = errors + 1
+      else
+         warnings = warnings + 1
+      end
+   end
+
+   return warnings, errors
+end
+
+local function format_file_report_header(report, file_name, opts)
+   local label = "Checking " .. file_name
+   local status
+
+   if report.fatal then
+      status = format_color(fatal_type(report), opts.color, "bright")
+   elseif #report == 0 then
+      status = format_color("OK", opts.color, "bright", "green")
+   else
+      local warnings, errors = count_warnings_errors(report)
+
+      if warnings > 0 then
+         status = format_color(tostring(warnings).." warning"..plural(warnings), opts.color, "bright", "red")
+      end
+
+      if errors > 0 then
+         status = status and (status.." / ") or ""
+         status = status..(format_color(tostring(errors).." error"..plural(errors), opts.color, "bright"))
+      end
+   end
+
+   return label .. (" "):rep(math.max(50 - #label, 1)) .. status
+end
+
+local function format_location(file, location, opts)
+   local res = ("%s:%d:%d"):format(file, location.line, location.column)
+
+   if opts.ranges then
+      res = ("%s-%d"):format(res, location.end_column)
+   end
+
+   return res
+end
+
+local function event_code(event)
+   return (event.code:sub(1, 1) == "0" and "E" or "W")..event.code
+end
+
+local function format_event(file_name, event, opts)
+   local message = format_message(event, opts.color)
+
+   if opts.codes then
+      message = ("(%s) %s"):format(event_code(event), message)
+   end
+
+   return format_location(file_name, event, opts) .. ": " .. message
+end
+
+local function format_file_report(report, file_name, opts)
+   local buf = {format_file_report_header(report, file_name, opts)}
+
+   if #report > 0 then
+      table.insert(buf, "")
+
+      for _, event in ipairs(report) do
+         table.insert(buf, "    " .. format_event(file_name, event, opts))
+      end
+
+      table.insert(buf, "")
+   elseif report.fatal then
+      table.insert(buf, "")
+      table.insert(buf, "    " .. file_name .. ": " .. report.msg)
+      table.insert(buf, "")
+   end
+
+   return table.concat(buf, "\n")
+end
+
+local function escape_xml(str)
+   str = str:gsub("&", "&amp;")
+   str = str:gsub('"', "&quot;")
+   str = str:gsub("'", "&apos;")
+   str = str:gsub("<", "&lt;")
+   str = str:gsub(">", "&gt;")
+   return str
+end
+
+format.builtin_formatters = {}
+
+function format.builtin_formatters.default(report, file_names, opts)
+   local buf = {}
+
+   if opts.quiet <= 2 then
+      for i, file_report in ipairs(report) do
+         if opts.quiet == 0 or file_report.fatal or #file_report > 0 then
+            table.insert(buf, (opts.quiet == 2 and format_file_report_header or format_file_report) (
+               file_report, file_names[i], opts))
+         end
+      end
+
+      if #buf > 0 and buf[#buf]:sub(-1) ~= "\n" then
+         table.insert(buf, "")
+      end
+   end
+
+   local total = ("Total: %s warning%s / %s error%s in %d file%s"):format(
+      format_number(report.warnings, opts.color), plural(report.warnings),
+      format_number(report.errors, opts.color), plural(report.errors),
+      #report - report.fatals, plural(#report - report.fatals))
+
+   if report.fatals > 0 then
+      total = total..(", couldn't check %s file%s"):format(
+         report.fatals, plural(report.fatals))
+   end
+
+   table.insert(buf, total)
+   return table.concat(buf, "\n")
+end
+
+function format.builtin_formatters.TAP(report, file_names, opts)
+   opts.color = false
+   local buf = {}
+
+   for i, file_report in ipairs(report) do
+      if file_report.fatal then
+         table.insert(buf, ("not ok %d %s: %s"):format(#buf + 1, file_names[i], fatal_type(file_report)))
+      elseif #file_report == 0 then
+         table.insert(buf, ("ok %d %s"):format(#buf + 1, file_names[i]))
+      else
+         for _, warning in ipairs(file_report) do
+            table.insert(buf, ("not ok %d %s"):format(#buf + 1, format_event(file_names[i], warning, opts)))
+         end
+      end
+   end
+
+   table.insert(buf, 1, "1.." .. tostring(#buf))
+   return table.concat(buf, "\n")
+end
+
+function format.builtin_formatters.JUnit(report, file_names)
+   -- JUnit formatter doesn't support any options.
+   local opts = {}
+   local buf = {[[<?xml version="1.0" encoding="UTF-8"?>]]}
+   local num_testcases = 0
+
+   for _, file_report in ipairs(report) do
+      if file_report.fatal or #file_report == 0 then
+         num_testcases = num_testcases + 1
+      else
+         num_testcases = num_testcases + #file_report
+      end
+   end
+
+   table.insert(buf, ([[<testsuite name="Luacheck report" tests="%d">]]):format(num_testcases))
+
+   for file_i, file_report in ipairs(report) do
+      if file_report.fatal then
+         table.insert(buf, ([[    <testcase name="%s" classname="%s">]]):format(
+            escape_xml(file_names[file_i]), escape_xml(file_names[file_i])))
+         table.insert(buf, ([[        <error type="%s"/>]]):format(escape_xml(fatal_type(file_report))))
+         table.insert(buf, [[    </testcase>]])
+      elseif #file_report == 0 then
+         table.insert(buf, ([[    <testcase name="%s" classname="%s"/>]]):format(
+            escape_xml(file_names[file_i]), escape_xml(file_names[file_i])))
+      else
+         for event_i, event in ipairs(file_report) do
+            table.insert(buf, ([[    <testcase name="%s:%d" classname="%s">]]):format(
+               escape_xml(file_names[file_i]), event_i, escape_xml(file_names[file_i])))
+            table.insert(buf, ([[        <failure type="%s" message="%s"/>]]):format(
+               escape_xml(event_code(event)), escape_xml(format_event(file_names[file_i], event, opts))))
+            table.insert(buf, [[    </testcase>]])
+         end
+      end
+   end
+
+   table.insert(buf, [[</testsuite>]])
+   return table.concat(buf, "\n")
+end
+
+local fatal_error_codes = {
+   ["I/O"] = "F1",
+   ["syntax"] = "F2",
+   ["runtime"] = "F3"
+}
+
+function format.builtin_formatters.visual_studio(report, file_names)
+   local buf = {}
+
+   for i, file_report in ipairs(report) do
+      if file_report.fatal then
+         -- Older docs suggest that line number after a file name is optional; newer docs mark it as required.
+         -- Just use tool name as origin and put file name into the message.
+         table.insert(buf, ("luacheck : fatal error %s: couldn't check %s: %s"):format(
+            fatal_error_codes[file_report.fatal], file_names[i], file_report.msg))
+      else
+         for _, event in ipairs(file_report) do
+               -- Older documentation on the format suggests that it could support column range.
+               -- Newer docs don't mention it. Don't use it for now.
+               local event_type = event.code:sub(1, 1) == "0" and "error" or "warning"
+               local message = format_message(event)
+               table.insert(buf, ("%s(%d,%d) : %s %s: %s"):format(
+                  file_names[i], event.line, event.column, event_type, event_code(event), message))
+         end
+      end
+   end
+
+   return table.concat(buf, "\n")
+end
+
+function format.builtin_formatters.plain(report, file_names, opts)
+   opts.color = false
+   local buf = {}
+
+   for i, file_report in ipairs(report) do
+      if file_report.fatal then
+         table.insert(buf, ("%s: %s (%s)"):format(file_names[i], fatal_type(file_report), file_report.msg))
+      else
+         for _, event in ipairs(file_report) do
+            table.insert(buf, format_event(file_names[i], event, opts))
+         end
+      end
+   end
+
+   return table.concat(buf, "\n")
+end
+
+--- Formats a report.
+-- Recognized options:
+--    `options.formatter`: name of used formatter. Default: "default".
+--    `options.quiet`: integer in range 0-3. See CLI. Default: 0.
+--    `options.color`: should use ansicolors? Default: true.
+--    `options.codes`: should output warning codes? Default: false.
+--    `options.ranges`: should output token end column? Default: false.
+function format.format(report, file_names, options)
+   return format.builtin_formatters[options.formatter or "default"](report, file_names, {
+      quiet = options.quiet or 0,
+      color = (options.color ~= false) and color_support,
+      codes = options.codes,
+      ranges = options.ranges
+   })
+end
+
+return format
diff --git a/script/luacheck/fs.lua b/script/luacheck/fs.lua
new file mode 100755
index 000000000..101e6a1f9
--- /dev/null
+++ b/script/luacheck/fs.lua
@@ -0,0 +1,219 @@
+local fs = {}
+
+local lfs = require "lfs"
+local utils = require "luacheck.utils"
+
+local function ensure_dir_sep(path)
+   if path:sub(-1) ~= utils.dir_sep then
+      return path .. utils.dir_sep
+   end
+
+   return path
+end
+
+function fs.split_base(path)
+   if utils.is_windows then
+      if path:match("^%a:\\") then
+         return path:sub(1, 3), path:sub(4)
+      else
+         -- Disregard UNC paths and relative paths with drive letter.
+         return "", path
+      end
+   else
+      if path:match("^/") then
+         if path:match("^//") then
+            return "//", path:sub(3)
+         else
+            return "/", path:sub(2)
+         end
+      else
+         return "", path
+      end
+   end
+end
+
+function fs.is_absolute(path)
+   return fs.split_base(path) ~= ""
+end
+
+function fs.normalize(path)
+   if utils.is_windows then
+      path = path:lower()
+   end
+   local base, rest = fs.split_base(path)
+   rest = rest:gsub("[/\\]", utils.dir_sep)
+
+   local parts = {}
+
+   for part in rest:gmatch("[^"..utils.dir_sep.."]+") do
+      if part ~= "." then
+         if part == ".." and #parts > 0 and parts[#parts] ~= ".." then
+            parts[#parts] = nil
+         else
+            parts[#parts + 1] = part
+         end
+      end
+   end
+
+   if base == "" and #parts == 0 then
+      return "."
+   else
+      return base..table.concat(parts, utils.dir_sep)
+   end
+end
+
+local function join_two_paths(base, path)
+   if base == "" or fs.is_absolute(path) then
+      return path
+   else
+      return ensure_dir_sep(base) .. path
+   end
+end
+
+function fs.join(base, ...)
+   local res = base
+
+   for i = 1, select("#", ...) do
+      res = join_two_paths(res, select(i, ...))
+   end
+
+   return res
+end
+
+function fs.is_subpath(path, subpath)
+   local base1, rest1 = fs.split_base(path)
+   local base2, rest2 = fs.split_base(subpath)
+
+   if base1 ~= base2 then
+      return false
+   end
+
+   if rest2:sub(1, #rest1) ~= rest1 then
+      return false
+   end
+
+   return rest1 == rest2 or rest2:sub(#rest1 + 1, #rest1 + 1) == utils.dir_sep
+end
+
+function fs.is_dir(path)
+   return lfs.attributes(path, "mode") == "directory"
+end
+
+function fs.is_file(path)
+   return lfs.attributes(path, "mode") == "file"
+end
+
+-- Searches for file starting from path, going up until the file
+-- is found or root directory is reached.
+-- Path must be absolute.
+-- Returns absolute and relative paths to directory containing file or nil.
+function fs.find_file(path, file)
+   if fs.is_absolute(file) then
+      return fs.is_file(file) and path, ""
+   end
+
+   path = fs.normalize(path)
+   local base, rest = fs.split_base(path)
+   local rel_path = ""
+
+   while true do
+      if fs.is_file(fs.join(base..rest, file)) then
+         return base..rest, rel_path
+      elseif rest == "" then
+         return
+      end
+
+      rest = rest:match("^(.*)"..utils.dir_sep..".*$") or ""
+      rel_path = rel_path..".."..utils.dir_sep
+   end
+end
+
+-- Returns iterator over directory items or nil, error message.
+function fs.dir_iter(dir_path)
+   local ok, iter, state, var = pcall(lfs.dir, dir_path)
+
+   if not ok then
+      local err = utils.unprefix(iter, "cannot open " .. dir_path .. ": ")
+      return nil, "couldn't list directory: " .. err
+   end
+
+   return iter, state, var
+end
+
+-- Returns list of all files in directory matching pattern.
+-- Additionally returns a mapping from directory paths that couldn't be expanded
+-- to error messages.
+function fs.extract_files(dir_path, pattern)
+   local res = {}
+   local err_map = {}
+
+   local function scan(dir)
+      local iter, state, var = fs.dir_iter(dir)
+
+      if not iter then
+         err_map[dir] = state
+         table.insert(res, dir)
+         return
+      end
+
+      for path in iter, state, var do
+         if path ~= "." and path ~= ".." then
+            local full_path = fs.join(dir, path)
+
+            if fs.is_dir(full_path) then
+               scan(full_path)
+            elseif path:match(pattern) and fs.is_file(full_path) then
+               table.insert(res, full_path)
+            end
+         end
+      end
+   end
+
+   scan(dir_path)
+   table.sort(res)
+   return res, err_map
+end
+
+local function make_absolute_dirs(dir_path)
+   if fs.is_dir(dir_path) then
+      return true
+   end
+
+   local upper_dir = fs.normalize(fs.join(dir_path, ".."))
+
+   if upper_dir == dir_path then
+      return nil, ("Filesystem root %s is not a directory"):format(upper_dir)
+   end
+
+   local upper_ok, upper_err = make_absolute_dirs(upper_dir)
+
+   if not upper_ok then
+      return nil, upper_err
+   end
+
+   local make_ok, make_error = lfs.mkdir(dir_path)
+
+   if not make_ok then
+      return nil, ("Couldn't make directory %s: %s"):format(dir_path, make_error)
+   end
+
+   return true
+end
+
+-- Ensures that a given path is a directory, creating intermediate directories if necessary.
+-- Returns true on success, nil and an error message on failure.
+function fs.make_dirs(dir_path)
+   return make_absolute_dirs(fs.normalize(fs.join(fs.get_current_dir(), dir_path)))
+end
+
+-- Returns modification time for a file.
+function fs.get_mtime(path)
+   return lfs.attributes(path, "modification")
+end
+
+-- Returns absolute path to current working directory, with trailing directory separator.
+function fs.get_current_dir()
+   return ensure_dir_sep(assert(lfs.currentdir()))
+end
+
+return fs
diff --git a/script/luacheck/globbing.lua b/script/luacheck/globbing.lua
new file mode 100755
index 000000000..b5337e182
--- /dev/null
+++ b/script/luacheck/globbing.lua
@@ -0,0 +1,161 @@
+local fs = require "luacheck.fs"
+local utils = require "luacheck.utils"
+
+-- Only ?, *, ** and simple character classes (with ranges and negation) are supported.
+-- Hidden files are not treated specially. Special characters can't be escaped.
+local globbing = {}
+
+local function is_regular_path(glob)
+   return not glob:find("[*?%[]")
+end
+
+local function get_parts(path)
+   local parts = {}
+
+   for part in path:gmatch("[^"..utils.dir_sep.."]+") do
+      table.insert(parts, part)
+   end
+
+   return parts
+end
+
+local function glob_pattern_escaper(c)
+   return ((c == "*" or c == "?") and "." or "%")..c
+end
+
+local function glob_range_escaper(c)
+   return c == "-" and c or ("%"..c)
+end
+
+local function glob_part_to_pattern(glob_part)
+   local buffer = {"^"}
+   local i = 1
+
+   while i <= #glob_part do
+      local bracketless
+      bracketless, i = glob_part:match("([^%[]*)()", i)
+      table.insert(buffer, (bracketless:gsub("%p", glob_pattern_escaper)))
+
+      if glob_part:sub(i, i) == "[" then
+         table.insert(buffer, "[")
+         i = i + 1
+         local first_char = glob_part:sub(i, i)
+
+         if first_char == "!" then
+            table.insert(buffer, "^")
+            i = i + 1
+         elseif first_char == "]" then
+            table.insert(buffer, "%]")
+            i = i + 1
+         end
+
+         bracketless, i = glob_part:match("([^%]]*)()", i)
+
+         if bracketless:sub(1, 1) == "-" then
+            table.insert(buffer, "%-")
+            bracketless = bracketless:sub(2)
+         end
+
+         local last_dash = ""
+
+         if bracketless:sub(-1) == "-" then
+            last_dash = "-"
+            bracketless = bracketless:sub(1, -2)
+         end
+
+         table.insert(buffer, (bracketless:gsub("%p", glob_range_escaper)))
+         table.insert(buffer, last_dash.."]")
+         i = i + 1
+      end
+   end
+
+   table.insert(buffer, "$")
+   return table.concat(buffer)
+end
+
+local function part_match(glob_part, path_part)
+   return utils.pmatch(path_part, glob_part_to_pattern(glob_part))
+end
+
+local function parts_match(glob_parts, glob_i, path_parts, path_i)
+   local glob_part = glob_parts[glob_i]
+
+   if not glob_part then
+      -- Reached glob end, path matches the glob or its subdirectory.
+      -- E.g. path "foo/bar/baz/src.lua" matches glob "foo/*/baz".
+      return true
+   end
+
+   if glob_part == "**" then
+      -- "**" can consume any number of path parts.
+      for i = path_i, #path_parts + 1 do
+         if parts_match(glob_parts, glob_i + 1, path_parts, i) then
+            return true
+         end
+      end
+
+      return false
+   end
+
+   local path_part = path_parts[path_i]
+   return path_part and part_match(glob_part, path_part) and (
+      parts_match(glob_parts, glob_i + 1, path_parts, path_i + 1))
+end
+
+-- Checks if a path matches a globbing pattern.
+-- Both must be absolute.
+function globbing.match(glob, path)
+   if is_regular_path(glob) then
+      return fs.is_subpath(glob, path)
+   end
+
+   local glob_base, path_base
+   glob_base, glob = fs.split_base(glob)
+   path_base, path = fs.split_base(path)
+
+   if glob_base ~= path_base then
+      return false
+   end
+
+   local glob_parts = get_parts(glob)
+   local path_parts = get_parts(path)
+   return parts_match(glob_parts, 1, path_parts, 1)
+end
+
+-- Checks if glob1 is less specific than glob2 and should be applied
+-- first in overrides.
+function globbing.compare(glob1, glob2)
+   local base1, base2
+   base1, glob1 = fs.split_base(glob1)
+   base2, glob2 = fs.split_base(glob2)
+
+   if base1 ~= base2 then
+      return base1 < base2
+   end
+
+   local parts1 = get_parts(glob1)
+   local parts2 = get_parts(glob2)
+
+   for i = 1, math.max(#parts1, #parts2) do
+      if not parts1[i] then
+         return true
+      elseif not parts2[i] then
+         return false
+      end
+
+      if (parts1[i] == "**" or parts2[i] == "**") and parts1[i] ~= parts2[i] then
+         return parts1[i] == "**"
+      end
+
+      local _, specials1 = parts1[i]:gsub("[%*%?%[]", {})
+      local _, specials2 = parts2[i]:gsub("[%*%?%[]", {})
+
+      if specials1 ~= specials2 then
+         return specials1 > specials2
+      end
+   end
+
+   return glob1 < glob2
+end
+
+return globbing
diff --git a/script/luacheck/init.lua b/script/luacheck/init.lua
new file mode 100755
index 000000000..7fe7aed0c
--- /dev/null
+++ b/script/luacheck/init.lua
@@ -0,0 +1,135 @@
+local check = require "luacheck.check"
+local filter = require "luacheck.filter"
+local options = require "luacheck.options"
+local format = require "luacheck.format"
+local utils = require "luacheck.utils"
+
+local luacheck = {
+   _VERSION = "0.23.0"
+}
+
+local function raw_validate_options(fname, opts, stds, context)
+   local ok, err = options.validate(options.all_options, opts, stds)
+
+   if not ok then
+      if context then
+         error(("bad argument #2 to '%s' (%s: %s)"):format(fname, context, err))
+      else
+         error(("bad argument #2 to '%s' (%s)"):format(fname, err))
+      end
+   end
+end
+
+local function validate_options(fname, items, opts, stds)
+   raw_validate_options(fname, opts)
+
+   if opts ~= nil then
+      for i in ipairs(items) do
+         raw_validate_options(fname, opts[i], stds, ("invalid options at index [%d]"):format(i))
+
+         if opts[i] ~= nil then
+            for j, nested_opts in ipairs(opts[i]) do
+               raw_validate_options(fname, nested_opts, stds, ("invalid options at index [%d][%d]"):format(i, j))
+            end
+         end
+      end
+   end
+end
+
+-- Returns report for a string.
+function luacheck.get_report(src)
+   local msg = ("bad argument #1 to 'luacheck.get_report' (string expected, got %s)"):format(type(src))
+   assert(type(src) == "string", msg)
+   return check(src)
+end
+
+-- Applies options to reports. Reports with .fatal field are unchanged.
+-- Options are applied to reports[i] in order: options, options[i], options[i][1], options[i][2], ...
+-- Returns new array of reports, adds .warnings, .errors and .fatals fields to this array.
+function luacheck.process_reports(reports, opts, stds)
+   local msg = ("bad argument #1 to 'luacheck.process_reports' (table expected, got %s)"):format(type(reports))
+   assert(type(reports) == "table", msg)
+   validate_options("luacheck.process_reports", reports, opts, stds)
+   local report = filter.filter(reports, opts, stds)
+   report.warnings = 0
+   report.errors = 0
+   report.fatals = 0
+
+   for _, file_report in ipairs(report) do
+      if file_report.fatal then
+         report.fatals = report.fatals + 1
+      else
+         for _, event in ipairs(file_report) do
+            if event.code:sub(1, 1) == "0" then
+               report.errors = report.errors + 1
+            else
+               report.warnings = report.warnings + 1
+            end
+         end
+      end
+   end
+
+   return report
+end
+
+-- Checks strings with options, returns report.
+-- Tables with .fatal field are unchanged.
+function luacheck.check_strings(srcs, opts)
+   local msg = ("bad argument #1 to 'luacheck.check_strings' (table expected, got %s)"):format(type(srcs))
+   assert(type(srcs) == "table", msg)
+
+   for _, item in ipairs(srcs) do
+      msg = ("bad argument #1 to 'luacheck.check_strings' (array of strings or tables expected, got %s)"):format(
+         type(item))
+      assert(type(item) == "string" or type(item) == "table", msg)
+   end
+
+   validate_options("luacheck.check_strings", srcs, opts)
+
+   local reports = {}
+
+   for i, src in ipairs(srcs) do
+      if type(src) == "table" and src.fatal then
+         reports[i] = src
+      else
+         reports[i] = luacheck.get_report(src)
+      end
+   end
+
+   return luacheck.process_reports(reports, opts)
+end
+
+function luacheck.check_files(files, opts)
+   local msg = ("bad argument #1 to 'luacheck.check_files' (table expected, got %s)"):format(type(files))
+   assert(type(files) == "table", msg)
+
+   for _, item in ipairs(files) do
+      msg = ("bad argument #1 to 'luacheck.check_files' (array of paths or file handles expected, got %s)"):format(
+         type(item))
+      assert(type(item) == "string" or io.type(item) == "file", msg
+      )
+   end
+
+   validate_options("luacheck.check_files", files, opts)
+
+   local srcs = {}
+
+   for i, file in ipairs(files) do
+      local src, err = utils.read_file(file)
+      srcs[i] = src or {fatal = "I/O", msg = err}
+   end
+
+   return luacheck.check_strings(srcs, opts)
+end
+
+function luacheck.get_message(issue)
+   local msg = ("bad argument #1 to 'luacheck.get_message' (table expected, got %s)"):format(type(issue))
+   assert(type(issue) == "table", msg)
+   return format.get_message(issue)
+end
+
+setmetatable(luacheck, {__call = function(_, ...)
+   return luacheck.check_files(...)
+end})
+
+return luacheck
diff --git a/script/luacheck/lexer.lua b/script/luacheck/lexer.lua
new file mode 100755
index 000000000..e626ea54e
--- /dev/null
+++ b/script/luacheck/lexer.lua
@@ -0,0 +1,749 @@
+local utils = require "luacheck.utils"
+
+-- Lexer should support syntax of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT(64bit and complex cdata literals).
+local lexer = {}
+
+local sbyte = string.byte
+local schar = string.char
+local sreverse = string.reverse
+local tconcat = table.concat
+local mfloor = math.floor
+
+-- No point in inlining these, fetching a constant ~= fetching a local.
+local BYTE_0, BYTE_9, BYTE_f, BYTE_F = sbyte("0"), sbyte("9"), sbyte("f"), sbyte("F")
+local BYTE_x, BYTE_X, BYTE_i, BYTE_I = sbyte("x"), sbyte("X"), sbyte("i"), sbyte("I")
+local BYTE_l, BYTE_L, BYTE_u, BYTE_U = sbyte("l"), sbyte("L"), sbyte("u"), sbyte("U")
+local BYTE_e, BYTE_E, BYTE_p, BYTE_P = sbyte("e"), sbyte("E"), sbyte("p"), sbyte("P")
+local BYTE_a, BYTE_z, BYTE_A, BYTE_Z = sbyte("a"), sbyte("z"), sbyte("A"), sbyte("Z")
+local BYTE_DOT, BYTE_COLON = sbyte("."), sbyte(":")
+local BYTE_OBRACK, BYTE_CBRACK = sbyte("["), sbyte("]")
+local BYTE_OBRACE, BYTE_CBRACE = sbyte("{"), sbyte("}")
+local BYTE_QUOTE, BYTE_DQUOTE = sbyte("'"), sbyte('"')
+local BYTE_PLUS, BYTE_DASH, BYTE_LDASH = sbyte("+"), sbyte("-"), sbyte("_")
+local BYTE_SLASH, BYTE_BSLASH = sbyte("/"), sbyte("\\")
+local BYTE_EQ, BYTE_NE = sbyte("="), sbyte("~")
+local BYTE_LT, BYTE_GT = sbyte("<"), sbyte(">")
+local BYTE_LF, BYTE_CR = sbyte("\n"), sbyte("\r")
+local BYTE_SPACE, BYTE_FF, BYTE_TAB, BYTE_VTAB = sbyte(" "), sbyte("\f"), sbyte("\t"), sbyte("\v")
+
+local function to_hex(b)
+   if BYTE_0 <= b and b <= BYTE_9 then
+      return b-BYTE_0
+   elseif BYTE_a <= b and b <= BYTE_f then
+      return 10+b-BYTE_a
+   elseif BYTE_A <= b and b <= BYTE_F then
+      return 10+b-BYTE_A
+   else
+      return nil
+   end
+end
+
+local function to_dec(b)
+   if BYTE_0 <= b and b <= BYTE_9 then
+      return b-BYTE_0
+   else
+      return nil
+   end
+end
+
+local function to_utf(codepoint)
+   if codepoint < 0x80 then  -- ASCII?
+      return schar(codepoint)
+   end
+
+   local buf = {}
+   local mfb = 0x3F
+
+   repeat
+      buf[#buf+1] = schar(codepoint % 0x40 + 0x80)
+      codepoint = mfloor(codepoint / 0x40)
+      mfb = mfloor(mfb / 2)
+   until codepoint <= mfb
+
+   buf[#buf+1] = schar(0xFE - mfb*2 + codepoint)
+   return sreverse(tconcat(buf))
+end
+
+local function is_alpha(b)
+   return (BYTE_a <= b and b <= BYTE_z) or
+      (BYTE_A <= b and b <= BYTE_Z) or b == BYTE_LDASH
+end
+
+local function is_newline(b)
+   return (b == BYTE_LF) or (b == BYTE_CR)
+end
+
+local function is_space(b)
+   return (b == BYTE_SPACE) or (b == BYTE_FF) or
+      (b == BYTE_TAB) or (b == BYTE_VTAB)
+end
+
+local keywords = utils.array_to_set({
+   "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in",
+   "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"})
+
+local simple_escapes = {
+   [sbyte("a")] = sbyte("\a"),
+   [sbyte("b")] = sbyte("\b"),
+   [sbyte("f")] = sbyte("\f"),
+   [sbyte("n")] = sbyte("\n"),
+   [sbyte("r")] = sbyte("\r"),
+   [sbyte("t")] = sbyte("\t"),
+   [sbyte("v")] = sbyte("\v"),
+   [BYTE_BSLASH] = BYTE_BSLASH,
+   [BYTE_QUOTE] = BYTE_QUOTE,
+   [BYTE_DQUOTE] = BYTE_DQUOTE
+}
+
+local function next_byte(state)
+   local offset = state.offset + 1
+   state.offset = offset
+   return state.src:get_codepoint(offset)
+end
+
+-- Skipping helpers.
+-- Take the current character, skip something, return next character.
+
+local function skip_newline(state, newline)
+   local first_newline_offset = state.offset
+   local b = next_byte(state)
+
+   if b ~= newline and is_newline(b) then
+      b = next_byte(state)
+   end
+
+   local line = state.line
+   local line_offsets = state.line_offsets
+   state.line_lengths[line] = first_newline_offset - line_offsets[line]
+   line = line + 1
+   state.line = line
+   line_offsets[line] = state.offset
+   return b
+end
+
+local function skip_to_newline(state, b)
+   while not is_newline(b) and b do
+      b = next_byte(state)
+   end
+
+   return b
+end
+
+local function skip_space(state, b)
+   while is_space(b) or is_newline(b) do
+      if is_newline(b) then
+         b = skip_newline(state, b)
+      else
+         b = next_byte(state)
+      end
+   end
+
+   return b
+end
+
+-- Skips "[=*" or "]=*". Returns next character and number of "="s.
+local function skip_long_bracket(state)
+   local start = state.offset
+   local b = next_byte(state)
+
+   while b == BYTE_EQ do
+      b = next_byte(state)
+   end
+
+   return b, state.offset-start-1
+end
+
+-- Token handlers.
+
+-- Called after the opening "[=*" has been skipped.
+-- Takes number of "=" in the opening bracket and token type(comment or string).
+local function lex_long_string(state, opening_long_bracket, token)
+   local b = next_byte(state)
+
+   if is_newline(b) then
+      b = skip_newline(state, b)
+   end
+
+   local lines = {}
+   local line_start = state.offset
+
+   while true do
+      if is_newline(b) then
+         -- Add the finished line.
+         lines[#lines+1] = state.src:get_substring(line_start, state.offset-1)
+
+         b = skip_newline(state, b)
+         line_start = state.offset
+      elseif b == BYTE_CBRACK then
+         local long_bracket
+         b, long_bracket = skip_long_bracket(state)
+
+         if b == BYTE_CBRACK and long_bracket == opening_long_bracket then
+            break
+         end
+      elseif b == nil then
+         return nil, token == "string" and "unfinished long string" or "unfinished long comment"
+      else
+         b = next_byte(state)
+      end
+   end
+
+   -- Add last line.
+   lines[#lines+1] = state.src:get_substring(line_start, state.offset-opening_long_bracket-2)
+   state.offset = state.offset + 1
+   return token, tconcat(lines, "\n")
+end
+
+local function lex_short_string(state, quote)
+   local b = next_byte(state)
+   local chunks  -- Buffer is only required when there are escape sequences.
+   local chunk_start = state.offset
+
+   while b ~= quote do
+      if b == BYTE_BSLASH then
+         -- Escape sequence.
+
+         if not chunks then
+            -- This is the first escape sequence, init buffer.
+            chunks = {}
+         end
+
+         -- Put previous chunk into buffer.
+         if chunk_start ~= state.offset then
+            chunks[#chunks+1] = state.src:get_substring(chunk_start, state.offset-1)
+         end
+
+         b = next_byte(state)
+
+         -- The final string escape sequence evaluates to.
+         local s
+
+         local escape_byte = simple_escapes[b]
+
+         if escape_byte then  -- Is it a simple escape sequence?
+            b = next_byte(state)
+            s = schar(escape_byte)
+         elseif is_newline(b) then
+            b = skip_newline(state, b)
+            s = "\n"
+         elseif b == BYTE_x then
+            -- Hexadecimal escape.
+            b = next_byte(state)  -- Skip "x".
+            -- Exactly two hexadecimal digits.
+            local c1, c2
+
+            if b then
+               c1 = to_hex(b)
+            end
+
+            if not c1 then
+               return nil, "invalid hexadecimal escape sequence", -2
+            end
+
+            b = next_byte(state)
+
+            if b then
+               c2 = to_hex(b)
+            end
+
+            if not c2 then
+               return nil, "invalid hexadecimal escape sequence", -3
+            end
+
+            b = next_byte(state)
+            s = schar(c1*16 + c2)
+         elseif b == BYTE_u then
+            b = next_byte(state)  -- Skip "u".
+
+            if b ~= BYTE_OBRACE then
+               return nil, "invalid UTF-8 escape sequence", -2
+            end
+
+            b = next_byte(state)  -- Skip "{".
+
+            local codepoint  -- There should be at least one digit.
+
+            if b then
+               codepoint = to_hex(b)
+            end
+
+            if not codepoint then
+               return nil, "invalid UTF-8 escape sequence", -3
+            end
+
+            local hexdigits = 0
+
+            while true do
+               b = next_byte(state)
+               local hex
+
+               if b then
+                  hex = to_hex(b)
+               end
+
+               if hex then
+                  hexdigits = hexdigits + 1
+                  codepoint = codepoint*16 + hex
+
+                  if codepoint > 0x10FFFF then
+                     -- UTF-8 value too large.
+                     return nil, "invalid UTF-8 escape sequence", -hexdigits-3
+                  end
+               else
+                  break
+               end
+            end
+
+            if b ~= BYTE_CBRACE then
+               return nil, "invalid UTF-8 escape sequence", -hexdigits-4
+            end
+
+            b = next_byte(state)  -- Skip "}".
+            s = to_utf(codepoint)
+         elseif b == BYTE_z then
+            -- Zap following span of spaces.
+            b = skip_space(state, next_byte(state))
+         else
+            -- Must be a decimal escape.
+            local cb
+
+            if b then
+               cb = to_dec(b)
+            end
+
+            if not cb then
+               return nil, "invalid escape sequence", -1
+            end
+
+            -- Up to three decimal digits.
+            b = next_byte(state)
+
+            if b then
+               local c2 = to_dec(b)
+
+               if c2 then
+                  cb = 10*cb + c2
+                  b = next_byte(state)
+
+                  if b then
+                     local c3 = to_dec(b)
+
+                     if c3 then
+                        cb = 10*cb + c3
+
+                        if cb > 255 then
+                           return nil, "invalid decimal escape sequence", -3
+                        end
+
+                        b = next_byte(state)
+                     end
+                  end
+               end
+            end
+
+            s = schar(cb)
+         end
+
+         if s then
+            chunks[#chunks+1] = s
+         end
+
+         -- Next chunk starts after escape sequence.
+         chunk_start = state.offset
+      elseif b == nil or is_newline(b) then
+         return nil, "unfinished string"
+      else
+         b = next_byte(state)
+      end
+   end
+
+   -- Offset now points at the closing quote.
+   local string_value
+
+   if chunks then
+      -- Put last chunk into buffer.
+      if chunk_start ~= state.offset then
+         chunks[#chunks+1] = state.src:get_substring(chunk_start, state.offset-1)
+      end
+
+      string_value = tconcat(chunks)
+   else
+      -- There were no escape sequences.
+      string_value = state.src:get_substring(chunk_start, state.offset-1)
+   end
+
+   -- Skip the closing quote.
+   state.offset = state.offset + 1
+   return "string", string_value
+end
+
+-- Payload for a number is simply a substring.
+-- Luacheck is supposed to be forward-compatible with Lua 5.3 and LuaJIT syntax, so
+--    parsing it into actual number may be problematic.
+-- It is not needed currently anyway as Luacheck does not do static evaluation yet.
+local function lex_number(state, b)
+   local start = state.offset
+
+   local exp_lower, exp_upper = BYTE_e, BYTE_E
+   local is_digit = to_dec
+   local has_digits = false
+   local is_float = false
+
+   if b == BYTE_0 then
+      b = next_byte(state)
+
+      if b == BYTE_x or b == BYTE_X then
+         exp_lower, exp_upper = BYTE_p, BYTE_P
+         is_digit = to_hex
+         b = next_byte(state)
+      else
+         has_digits = true
+      end
+   end
+
+   while b ~= nil and is_digit(b) do
+      b = next_byte(state)
+      has_digits = true
+   end
+
+   if b == BYTE_DOT then
+      -- Fractional part.
+      is_float = true
+      b = next_byte(state)  -- Skip dot.
+
+      while b ~= nil and is_digit(b) do
+         b = next_byte(state)
+         has_digits = true
+      end
+   end
+
+   if b == exp_lower or b == exp_upper then
+      -- Exponent part.
+      is_float = true
+      b = next_byte(state)
+
+      -- Skip optional sign.
+      if b == BYTE_PLUS or b == BYTE_DASH then
+         b = next_byte(state)
+      end
+
+      -- Exponent consists of one or more decimal digits.
+      if b == nil or not to_dec(b) then
+         return nil, "malformed number"
+      end
+
+      repeat
+         b = next_byte(state)
+      until b == nil or not to_dec(b)
+   end
+
+   if not has_digits then
+      return nil, "malformed number"
+   end
+
+   -- Is it cdata literal?
+   if b == BYTE_i or b == BYTE_I then
+      -- It is complex literal. Skip "i" or "I".
+      state.offset = state.offset + 1
+   else
+      -- uint64_t and int64_t literals can not be fractional.
+      if not is_float then
+         if b == BYTE_u or b == BYTE_U then
+            -- It may be uint64_t literal.
+            local b1 = state.src:get_codepoint(state.offset+1)
+
+            if b1 == BYTE_l or b1 == BYTE_L then
+               local b2 = state.src:get_codepoint(state.offset+2)
+
+               if b2 == BYTE_l or b2 == BYTE_L then
+                  -- It is uint64_t literal.
+                  state.offset = state.offset + 3
+               end
+            end
+         elseif b == BYTE_l or b == BYTE_L then
+            -- It may be uint64_t or int64_t literal.
+            local b1 = state.src:get_codepoint(state.offset+1)
+
+            if b1 == BYTE_l or b1 == BYTE_L then
+               local b2 = state.src:get_codepoint(state.offset+2)
+
+               if b2 == BYTE_u or b2 == BYTE_U then
+                  -- It is uint64_t literal.
+                  state.offset = state.offset + 3
+               else
+                  -- It is int64_t literal.
+                  state.offset = state.offset + 2
+               end
+            end
+         end
+      end
+   end
+
+   return "number", state.src:get_substring(start, state.offset-1)
+end
+
+local function lex_ident(state)
+   local start = state.offset
+   local b = next_byte(state)
+
+   while (b ~= nil) and (is_alpha(b) or to_dec(b)) do
+      b = next_byte(state)
+   end
+
+   local ident = state.src:get_substring(start, state.offset-1)
+
+   if keywords[ident] then
+      return ident
+   else
+      return "name", ident
+   end
+end
+
+local function lex_dash(state)
+   local b = next_byte(state)
+
+   -- Is it "-" or comment?
+   if b ~= BYTE_DASH then
+      return "-"
+   end
+
+   -- It is a comment.
+   b = next_byte(state)
+   local start = state.offset
+
+   -- Is it a long comment?
+   if b == BYTE_OBRACK then
+      local long_bracket
+      b, long_bracket = skip_long_bracket(state)
+
+      if b == BYTE_OBRACK then
+         return lex_long_string(state, long_bracket, "long_comment")
+      end
+   end
+
+   -- Short comment.
+   skip_to_newline(state, b)
+   local comment_value = state.src:get_substring(start, state.offset - 1)
+   return "short_comment", comment_value
+end
+
+local function lex_bracket(state)
+   -- Is it "[" or long string?
+   local b, long_bracket = skip_long_bracket(state)
+
+   if b == BYTE_OBRACK then
+      return lex_long_string(state, long_bracket, "string")
+   elseif long_bracket == 0 then
+      return "["
+   else
+      return nil, "invalid long string delimiter"
+   end
+end
+
+local function lex_eq(state)
+   local b = next_byte(state)
+
+   if b == BYTE_EQ then
+      state.offset = state.offset + 1
+      return "=="
+   else
+      return "="
+   end
+end
+
+local function lex_lt(state)
+   local b = next_byte(state)
+
+   if b == BYTE_EQ then
+      state.offset = state.offset + 1
+      return "<="
+   elseif b == BYTE_LT then
+      state.offset = state.offset + 1
+      return "<<"
+   else
+      return "<"
+   end
+end
+
+local function lex_gt(state)
+   local b = next_byte(state)
+
+   if b == BYTE_EQ then
+      state.offset = state.offset + 1
+      return ">="
+   elseif b == BYTE_GT then
+      state.offset = state.offset + 1
+      return ">>"
+   else
+      return ">"
+   end
+end
+
+local function lex_div(state)
+   local b = next_byte(state)
+
+   if b == BYTE_SLASH then
+      state.offset = state.offset + 1
+      return "//"
+   else
+      return "/"
+   end
+end
+
+local function lex_ne(state)
+   local b = next_byte(state)
+
+   if b == BYTE_EQ then
+      state.offset = state.offset + 1
+      return "~="
+   else
+      return "~"
+   end
+end
+
+local function lex_colon(state)
+   local b = next_byte(state)
+
+   if b == BYTE_COLON then
+      state.offset = state.offset + 1
+      return "::"
+   else
+      return ":"
+   end
+end
+
+local function lex_dot(state)
+   local b = next_byte(state)
+
+   if b == BYTE_DOT then
+      b = next_byte(state)
+
+      if b == BYTE_DOT then
+         state.offset = state.offset + 1
+         return "...", "..."
+      else
+         return ".."
+      end
+   elseif b and to_dec(b) then
+      -- Backtrack to dot.
+      state.offset = state.offset - 2
+      return lex_number(state, next_byte(state))
+   else
+      return "."
+   end
+end
+
+local function lex_any(state, b)
+   state.offset = state.offset + 1
+
+   if b > 255 then
+      b = 255
+   end
+
+   return schar(b)
+end
+
+-- Maps first bytes of tokens to functions that handle them.
+-- Each handler takes the first byte as an argument.
+-- Each handler stops at the character after the token and returns the token and,
+--    optionally, a value associated with the token.
+-- On error handler returns nil, error message and, optionally, start of reported location as negative offset.
+local byte_handlers = {
+   [BYTE_DOT] = lex_dot,
+   [BYTE_COLON] = lex_colon,
+   [BYTE_OBRACK] = lex_bracket,
+   [BYTE_QUOTE] = lex_short_string,
+   [BYTE_DQUOTE] = lex_short_string,
+   [BYTE_DASH] = lex_dash,
+   [BYTE_SLASH] = lex_div,
+   [BYTE_EQ] = lex_eq,
+   [BYTE_NE] = lex_ne,
+   [BYTE_LT] = lex_lt,
+   [BYTE_GT] = lex_gt,
+   [BYTE_LDASH] = lex_ident
+}
+
+for b=BYTE_0, BYTE_9 do
+   byte_handlers[b] = lex_number
+end
+
+for b=BYTE_a, BYTE_z do
+   byte_handlers[b] = lex_ident
+end
+
+for b=BYTE_A, BYTE_Z do
+   byte_handlers[b] = lex_ident
+end
+
+-- Creates and returns lexer state for source.
+function lexer.new_state(src, line_offsets, line_lengths)
+   local state = {
+      src = src,
+      line = 1,
+      line_offsets = line_offsets or {},
+      line_lengths = line_lengths or {},
+      offset = 1
+   }
+
+   state.line_offsets[1] = 1
+
+   if src:get_length() >= 2 and src:get_substring(1, 2) == "#!" then
+      -- Skip shebang line.
+      state.offset = 2
+      skip_to_newline(state, next_byte(state))
+   end
+
+   return state
+end
+
+function lexer.get_quoted_substring_or_line(state, line, offset, end_offset)
+   local line_length = state.line_lengths[line]
+
+   if line_length then
+      local line_end_offset = state.line_offsets[line] + line_length - 1
+
+      if line_end_offset < end_offset then
+         end_offset = line_end_offset
+      end
+   end
+
+   return "'" .. state.src:get_printable_substring(offset, end_offset) .. "'"
+end
+
+-- Looks for next token starting from state.line, state.offset.
+-- Returns next token, its value and its location (line, offset).
+-- Sets state.line, state.offset to token end location + 1.
+-- Fills state.line_offsets and state.line_lengths.
+-- On error returns nil, error message, error location (line, offset), error end offset.
+function lexer.next_token(state)
+   local line_offsets = state.line_offsets
+   local b = skip_space(state, state.src:get_codepoint(state.offset))
+
+   -- Save location of token start.
+   local token_line = state.line
+   local line_offset = line_offsets[token_line]
+   local token_offset = state.offset
+
+   if not b then
+      -- EOF token has length 1.
+      state.offset = state.offset + 1
+      state.line_lengths[token_line] = token_offset - line_offset
+      return "eof", nil, token_line, token_offset
+   end
+
+   local token, token_value, relative_error_offset = (byte_handlers[b] or lex_any)(state, b)
+
+   if relative_error_offset then
+      -- Error relative to current offset.
+      local error_offset = state.offset + relative_error_offset
+      local error_end_offset = math.min(state.offset, state.src:get_length())
+      local error_message = token_value .. " " .. lexer.get_quoted_substring_or_line(state,
+         state.line, error_offset, error_end_offset)
+      return nil, error_message, state.line, error_offset, error_end_offset
+   end
+
+   -- Single character errors fall through here.
+   return token, token_value, token_line, token_offset, not token and token_offset
+end
+
+return lexer
diff --git a/script/luacheck/main.lua b/script/luacheck/main.lua
new file mode 100755
index 000000000..8366ee154
--- /dev/null
+++ b/script/luacheck/main.lua
@@ -0,0 +1,353 @@
+local argparse = require "argparse"
+local cache = require "luacheck.cache"
+local config = require "luacheck.config"
+local luacheck = require "luacheck"
+local multithreading = require "luacheck.multithreading"
+local profiler = require "luacheck.profiler"
+local runner = require "luacheck.runner"
+local utils = require "luacheck.utils"
+local version = require "luacheck.version"
+
+local exit_codes = {
+   ok = 0,
+   warnings = 1,
+   errors = 2,
+   fatals = 3,
+   critical = 4
+}
+
+local function critical(msg)
+   io.stderr:write("Critical error: "..msg.."\n")
+   os.exit(exit_codes.critical)
+end
+
+local function get_parser()
+   local parser = argparse(
+      "luacheck", "luacheck " .. luacheck._VERSION .. ", a linter and a static analyzer for Lua.", [[
+Links:
+
+   Luacheck on GitHub: https://github.com/mpeterv/luacheck
+   Luacheck documentation: https://luacheck.readthedocs.org]])
+      :help_max_width(80)
+
+   parser:argument("files", "List of files, directories and rockspecs to check. Pass '-' to check stdin.")
+      :args "+"
+      :argname "<file>"
+
+   parser:group("Options for filtering warnings",
+      parser:flag("-g --no-global", "Filter out warnings related to global variables. " ..
+         "Equivalent to --ignore 1."):target("global"):action("store_false"),
+      parser:flag("-u --no-unused", "Filter out warnings related to unused variables and values. " ..
+         "Equivalent to --ignore [23]."):target("unused"):action("store_false"),
+      parser:flag("-r --no-redefined", "Filter out warnings related to redefined variables. " ..
+         "Equivalent to --ignore 4."):target("redefined"):action("store_false"),
+
+      parser:flag("-a --no-unused-args", "Filter out warnings related to unused arguments and " ..
+         "loop variables. Equivalent to --ignore 21[23]."):target("unused_args"):action("store_false"),
+      parser:flag("-s --no-unused-secondaries", "Filter out warnings related to unused variables set " ..
+         "together with used ones."):target("unused_secondaries"):action("store_false"),
+      parser:flag("--no-self", "Filter out warnings related to implicit self argument.")
+         :target("self"):action("store_false"),
+
+      parser:option("--ignore -i", "Filter out warnings matching these patterns.\n" ..
+         "If a pattern contains slash, part before slash matches warning code and part after it matches name of " ..
+         "related variable. Otherwise, if the pattern contains letters or underscore, it matches name of " ..
+         "related variable. Otherwise, the pattern matches warning code.")
+         :args "+"
+         :count "*"
+         :argname "<patt>"
+         :action "concat"
+         :init(nil),
+      parser:option("--enable -e", "Do not filter out warnings matching these patterns.")
+         :args "+"
+         :count "*"
+         :argname "<patt>"
+         :action "concat"
+         :init(nil),
+      parser:option("--only -o", "Filter out warnings not matching these patterns.")
+         :args "+"
+         :count "*"
+         :argname "<patt>"
+         :action "concat"
+         :init(nil))
+
+   parser:group("Options for configuring allowed globals",
+      parser:option("--std", "Set standard globals, default is max. <std> can be one of:\n" ..
+         "   max - union of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x;\n" ..
+         "   min - intersection of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x;\n" ..
+         "   lua51 - globals of Lua 5.1 without deprecated ones;\n" ..
+         "   lua51c - globals of Lua 5.1;\n" ..
+         "   lua52 - globals of Lua 5.2;\n" ..
+         "   lua52c - globals of Lua 5.2 with LUA_COMPAT_ALL;\n" ..
+         "   lua53 - globals of Lua 5.3;\n" ..
+         "   lua53c - globals of Lua 5.3 with LUA_COMPAT_5_2;\n" ..
+         "   luajit - globals of LuaJIT 2.x;\n" ..
+         "   ngx_lua - globals of Openresty lua-nginx-module 0.10.10, including standard LuaJIT 2.x globals;\n" ..
+         "   love - globals added by LÖVE;\n" ..
+         "   busted - globals added by Busted 2.0, by default added for files ending with _spec.lua within spec, " ..
+         "test, and tests subdirectories;\n" ..
+         "   rockspec - globals allowed in rockspecs, by default added for files ending with .rockspec;\n" ..
+         "   luacheckrc - globals allowed in Luacheck configs, by default added for files ending with .luacheckrc;\n" ..
+         "   none - no standard globals.\n\n" ..
+         "Sets can be combined using '+'. Extra sets can be defined in config by " ..
+         "adding to `stds` global in config."),
+      parser:flag("-c --compat", "Equivalent to --std max."),
+
+      parser:option("--globals", "Add custom global variables (e.g. foo) or fields (e.g. foo.bar) " ..
+         "on top of standard ones.")
+         :args "*"
+         :count "*"
+         :argname "<name>"
+         :action "concat"
+         :init(nil),
+      parser:option("--read-globals", "Add read-only global variables or fields.")
+         :args "*"
+         :count "*"
+         :argname "<name>"
+         :action "concat"
+         :init(nil),
+      parser:option("--new-globals", "Set custom global variables or fields. Removes custom globals added previously.")
+         :args "*"
+         :count "*"
+         :argname "<name>"
+         :action "concat"
+         :init(nil),
+      parser:option("--new-read-globals", "Set read-only global variables or fields. " ..
+         "Removes read-only globals added previously.")
+         :args "*"
+         :count "*"
+         :argname "<name>"
+         :action "concat"
+         :init(nil),
+      parser:option("--not-globals", "Remove custom and standard global variables or fields.")
+         :args "*"
+         :count "*"
+         :argname "<name>"
+         :action "concat"
+         :init(nil),
+
+      parser:flag("-d --allow-defined", "Allow defining globals implicitly by setting them."),
+      parser:flag("-t --allow-defined-top",
+         "Allow defining globals implicitly by setting them in the top level scope."),
+      parser:flag("-m --module", "Limit visibility of implicitly defined globals to their files."))
+
+   parser:group("Options for configuring line length limits",
+      parser:option("--max-line-length", "Set maximum allowed line length (default: 120).")
+         :argname "<length>"
+         :convert(tonumber),
+      parser:flag("--no-max-line-length", "Do not limit line length.")
+         :action "store_false"
+         :target "max_line_length",
+
+      parser:option("--max-code-line-length", "Set maximum allowed length for lines ending with code (default: 120).")
+         :argname "<length>"
+         :convert(tonumber),
+      parser:flag("--no-max-code-line-length", "Do not limit code line length.")
+         :action "store_false"
+         :target "max_code_line_length",
+
+      parser:option("--max-string-line-length", "Set maximum allowed length for lines within a string (default: 120).")
+         :argname "<length>"
+         :convert(tonumber),
+      parser:flag("--no-max-string-line-length", "Do not limit string line length.")
+         :action "store_false"
+         :target "max_string_line_length",
+
+      parser:option("--max-comment-line-length", "Set maximum allowed length for comment lines (default: 120).")
+         :argname "<length>"
+         :convert(tonumber),
+      parser:flag("--no-max-comment-line-length", "Do not limit comment line length.")
+         :action "store_false"
+         :target "max_comment_line_length")
+
+   parser:option("--max-cyclomatic-complexity", "Set maximum cyclomatic complexity for functions.")
+      :argname "<complexity>"
+      :convert(tonumber)
+   parser:flag("--no-max-cyclomatic-complexity", "Do not limit function cyclomatic complexity (default).")
+      :action "store_false"
+      :target "max_cyclomatic_complexity"
+
+   local default_global_path = config.get_default_global_path()
+
+   local config_opt = parser:option("--config", "Path to configuration file. (default: "..config.default_path..")")
+   local no_config_opt = parser:flag("--no-config", "Do not look up configuration file.")
+      :action "store_false"
+      :target "config"
+
+   parser:mutex(config_opt, no_config_opt)
+
+   local default_config_opt = parser:option("--default-config", ("Path to configuration file to use if --[no-]config "..
+      "is not used and project-specific %s is not found. (default: %s)"):format(
+         config.default_path, default_global_path or "could not detect"))
+   local no_default_config_opt = parser:flag("--no-default-config", "Do not use default configuration file.")
+      :action "store_false"
+      :target "default_config"
+
+   parser:mutex(default_config_opt, no_default_config_opt)
+
+   parser:group("Configuration file options",
+      config_opt,
+      no_config_opt,
+      default_config_opt,
+      no_default_config_opt)
+
+   parser:group("File filtering options",
+      parser:option("--exclude-files", "Do not check files matching these globbing patterns.")
+         :args "+"
+         :count "*"
+         :argname "<glob>"
+         :action "concat"
+         :init(nil),
+      parser:option("--include-files", "Do not check files not matching these globbing patterns.")
+         :args "+"
+         :count "*"
+         :argname "<glob>"
+         :action "concat"
+         :init(nil))
+
+   parser:option("--filename", "Use another filename in output and for selecting configuration overrides.")
+
+   local cache_opt = parser:option("--cache", ("Path to cache directory. (default: %s)"):format(
+         cache.get_default_dir()))
+      :args "?"
+
+   local no_cache_opt = parser:flag("--no-cache", "Do not use cache.")
+      :action "store_false"
+      :target "cache"
+
+   parser:mutex(cache_opt, no_cache_opt)
+
+   local lanes_notice = ""
+
+   if not multithreading.has_lanes then
+      lanes_notice = "\nWarning: LuaLanes not found, parallel checking disabled."
+   end
+
+   parser:group("Performance optimization options",
+      cache_opt,
+      no_cache_opt,
+      parser:option(
+         "-j --jobs", "Check <jobs> files in parallel (default: " ..
+            tostring(multithreading.default_jobs) .. ")." .. lanes_notice):convert(tonumber))
+
+   parser:group("Output formatting options",
+      parser:option("--formatter" , "Use custom formatter. <formatter> must be a module name or one of:\n" ..
+         "   TAP - Test Anything Protocol formatter;\n" ..
+         "   JUnit - JUnit XML formatter;\n" ..
+         "   visual_studio - MSBuild/Visual Studio aware formatter;\n" ..
+         "   plain - simple warning-per-line formatter;\n" ..
+         "   default - standard formatter."),
+      parser:flag("-q --quiet", "Suppress output for files without warnings.\n" ..
+         "-qq: Suppress output of warnings.\n" ..
+         "-qqq: Only print total number of warnings and errors.")
+         :count "0-3",
+      parser:flag("--codes", "Show warning codes."),
+      parser:flag("--ranges", "Show ranges of columns related to warnings."),
+      parser:flag("--no-color", "Do not color output.")
+         :action "store_false"
+         :target "color")
+
+   parser:flag("--profile", "Show performance statistics."):hidden(true)
+
+   parser:flag("-v --version", "Show version info and exit.")
+      :action(function() print(version.string) os.exit(exit_codes.ok) end)
+
+   return parser
+end
+
+local function main()
+   local parser = get_parser()
+   local ok, args = parser:pparse()
+   if not ok then
+      io.stderr:write(("%s\n\nError: %s\n"):format(parser:get_usage(), args))
+      os.exit(exit_codes.critical)
+   end
+
+   if args.profile then
+      args.jobs = 1
+      profiler.init()
+   end
+
+   if args.quiet == 0 then
+      args.quiet = nil
+   end
+
+   if args.cache then
+      args.cache = args.cache[1] or true
+   end
+
+   local checker, err, is_invalid_args_error = runner.new(args)
+
+   if not checker then
+      if is_invalid_args_error then
+         io.stderr:write(("%s\n\nError: %s\n"):format(parser:get_usage(), err))
+         os.exit(exit_codes.critical)
+      else
+         critical(err)
+      end
+   end
+
+   local inputs = {}
+
+   for _, file in ipairs(args.files) do
+      local input = {filename = args.filename}
+
+      if file == "-" then
+         input.file = io.stdin
+      elseif file:find("%.rockspec$") then
+         input.rockspec_path = file
+      else
+         input.path = file
+      end
+
+      table.insert(inputs, input)
+   end
+
+   local report, check_err = checker:check(inputs)
+
+   if not report then
+      critical(check_err)
+   end
+
+   for _, file_report in ipairs(report) do
+      if not file_report.filename then
+         file_report.filename = "stdin"
+      end
+   end
+
+   local output, format_err = checker:format(report)
+
+   if not output then
+      critical(format_err)
+   end
+
+   io.stdout:write(output)
+
+   if args.profile then
+      profiler.report()
+   end
+
+   if report.fatals > 0 then
+      os.exit(exit_codes.fatals)
+   elseif report.errors > 0 then
+      os.exit(exit_codes.errors)
+   elseif report.warnings > 0 then
+      os.exit(exit_codes.warnings)
+   else
+      os.exit(exit_codes.ok)
+   end
+end
+
+local _, error_wrapper = utils.try(main)
+local err = error_wrapper.err
+local traceback = error_wrapper.traceback
+
+if utils.is_instance(err, utils.InvalidPatternError) then
+   critical(("Invalid pattern '%s'"):format(err.pattern))
+elseif type(err) == "string" and err:find("interrupted!$") then
+   critical("Interrupted")
+else
+   local msg = ("Luacheck %s bug (please report at https://github.com/mpeterv/luacheck/issues):\n%s\n%s"):format(
+      luacheck._VERSION, err, traceback)
+   critical(msg)
+end
diff --git a/script/luacheck/multithreading.lua b/script/luacheck/multithreading.lua
new file mode 100755
index 000000000..55df71657
--- /dev/null
+++ b/script/luacheck/multithreading.lua
@@ -0,0 +1,105 @@
+local utils = require "luacheck.utils"
+
+local multithreading = {}
+
+local lanes_ok, lanes = pcall(require, "lanes")
+lanes_ok = lanes_ok and pcall(lanes.configure)
+multithreading.has_lanes = lanes_ok
+multithreading.lanes = lanes
+multithreading.default_jobs = 1
+
+if not lanes_ok then
+   return multithreading
+end
+
+local cpu_number_detection_commands = {}
+
+if utils.is_windows then
+   cpu_number_detection_commands[1] = "echo %NUMBER_OF_PROCESSORS%"
+else
+   cpu_number_detection_commands[1] = "getconf _NPROCESSORS_ONLN 2>&1"
+   cpu_number_detection_commands[2] = "sysctl -n hw.ncpu 2>&1"
+   cpu_number_detection_commands[3] = "psrinfo -p 2>&1"
+end
+
+for _, command in ipairs(cpu_number_detection_commands) do
+   local handler = io.popen(command)
+
+   if handler then
+      local output = handler:read("*a")
+      handler:close()
+
+      if output then
+         local cpu_number = tonumber(utils.strip(output))
+
+         if cpu_number then
+            multithreading.default_jobs = math.floor(math.max(cpu_number, 1))
+            break
+         end
+      end
+   end
+end
+
+-- Reads pairs {key, arg} from given linda slot until it gets nil as arg.
+-- Returns table with pairs [key] = func(arg).
+local function worker_task(linda, input_slot, func)
+   local results = {}
+
+   while true do
+      local _, pair = linda:receive(nil, input_slot)
+      local key, arg = pair[1], pair[2]
+
+      if arg == nil then
+         return results
+      end
+
+      results[key] = func(arg)
+   end
+end
+
+local function protected_worker_task(...)
+   return true, utils.try(worker_task, ...)
+end
+
+local worker_gen = lanes.gen("*", protected_worker_task)
+
+-- Maps func over array, performing at most jobs calls in parallel.
+function multithreading.pmap(func, array, jobs)
+   jobs = jobs or multithreading.default_jobs
+   jobs = math.min(jobs, #array)
+
+   if jobs < 2 then
+      return utils.map(func, array)
+   end
+
+   local workers = {}
+   local linda = lanes.linda()
+
+   for i = 1, jobs do
+      workers[i] = worker_gen(linda, 0, func)
+   end
+
+   for i, item in ipairs(array) do
+      linda:send(nil, 0, {i, item})
+   end
+
+   for _ = 1, jobs do
+      linda:send(nil, 0, {})
+   end
+
+   local results = {}
+
+   for _, worker in ipairs(workers) do
+      local _, ok, worker_results = assert(worker:join())
+
+      if ok then
+         utils.update(results, worker_results)
+      else
+         error(worker_results, 0)
+      end
+   end
+
+   return results
+end
+
+return multithreading
diff --git a/script/luacheck/options.lua b/script/luacheck/options.lua
new file mode 100755
index 000000000..991eb7e39
--- /dev/null
+++ b/script/luacheck/options.lua
@@ -0,0 +1,419 @@
+local options = {}
+
+local builtin_standards = require "luacheck.builtin_standards"
+local standards = require "luacheck.standards"
+local utils = require "luacheck.utils"
+
+local boolean = utils.has_type("boolean")
+local number_or_false = utils.has_type_or_false("number")
+local array_of_strings = utils.array_of("string")
+
+-- Validates std string.
+-- Returns an array of std names with `add` field if there is `+` at the beginning of the string.
+-- On validation error returns `nil` and an error message.
+local function split_std(std, stds)
+   local parts = utils.split(std, "+")
+
+   if parts[1]:match("^%s*$") then
+      parts.add = true
+      table.remove(parts, 1)
+   end
+
+   for i, part in ipairs(parts) do
+      parts[i] = utils.strip(part)
+
+      if not stds[parts[i]] then
+         return nil, ("unknown std '%s'"):format(parts[i])
+      end
+   end
+
+   return parts
+end
+
+local function std_or_array_of_strings(x, stds)
+   if type(x) == "string" then
+      local ok, err = split_std(x, stds)
+      return not not ok, err
+   elseif type(x) == "table" then
+      return standards.validate_std_table(x)
+   else
+      return false, "string or table expected, got " .. type(x)
+   end
+end
+
+local function field_map(x)
+   if type(x) == "table" then
+      return standards.validate_globals_table(x)
+   else
+      return false, "table expected, got " .. type(x)
+   end
+end
+
+options.nullary_inline_options = {
+   global = boolean,
+   unused = boolean,
+   redefined = boolean,
+   unused_args = boolean,
+   unused_secondaries = boolean,
+   self = boolean,
+   compat = boolean,
+   allow_defined = boolean,
+   allow_defined_top = boolean,
+   module = boolean
+}
+
+options.variadic_inline_options = {
+   globals = field_map,
+   read_globals = field_map,
+   new_globals = field_map,
+   new_read_globals = field_map,
+   not_globals = array_of_strings,
+   ignore = array_of_strings,
+   enable = array_of_strings,
+   only = array_of_strings
+}
+
+options.all_options = {
+   std = std_or_array_of_strings,
+   max_line_length = number_or_false,
+   max_code_line_length = number_or_false,
+   max_string_line_length = number_or_false,
+   max_comment_line_length = number_or_false,
+   max_cyclomatic_complexity = number_or_false
+}
+
+utils.update(options.all_options, options.nullary_inline_options)
+utils.update(options.all_options, options.variadic_inline_options)
+
+-- Returns true if opts is valid option_set or is nil.
+-- Otherwise returns false and an error message.
+function options.validate(option_set, opts, stds)
+   if opts == nil then
+      return true
+   end
+
+   if type(opts) ~= "table" then
+      return false, "option table expected, got " .. type(opts)
+   end
+
+   stds = stds or builtin_standards
+
+   for option, validator in utils.sorted_pairs(option_set) do
+      if opts[option] ~= nil then
+         local ok, err = validator(opts[option], stds)
+
+         if not ok then
+            return false, ("invalid value of option '%s': %s"):format(option, err)
+         end
+      end
+   end
+
+   return true
+end
+
+-- Option stack is an array of options with options closer to end
+-- overriding options closer to beginning.
+
+-- Extracts sequence of active std tables from an option stack.
+local function get_std_tables(opts_stack, stds)
+   local base_std
+   local add_stds = {}
+   local no_compat = false
+
+   for _, opts in utils.ripairs(opts_stack) do
+      if opts.compat and not no_compat then
+         base_std = stds.max
+         break
+      elseif opts.compat == false then
+         no_compat = true
+      end
+
+      if opts.std then
+         if type(opts.std) == "table" then
+            base_std = opts.std
+            break
+         else
+            local parts = split_std(opts.std, stds)
+
+            for _, part in ipairs(parts) do
+               table.insert(add_stds, stds[part])
+            end
+
+            if not parts.add then
+               base_std = {}
+               break
+            end
+         end
+      end
+   end
+
+   table.insert(add_stds, 1, base_std or stds.max)
+   return add_stds
+end
+
+-- Returns index of the last option table in a stack that uses given option,
+-- or zero if the option isn't used anywhere.
+local function index_of_last_option_usage(opts_stack, option_name)
+   for index, opts in utils.ripairs(opts_stack) do
+      if opts[option_name] then
+         return index
+      end
+   end
+
+   return 0
+end
+
+local function split_field(field_name)
+   return utils.split(field_name, "%.")
+end
+
+local function field_comparator(field1, field2)
+   local parts1 = field1[1]
+   local parts2 = field2[1]
+
+   for i = 1, math.max(#parts1, #parts2) do
+      local part1 = parts1[i]
+      local part2 = parts2[i]
+
+      if not part1 then
+         return true
+      elseif not part2 then
+         return false
+      end
+
+      if part1 ~= part2 then
+         return part1 < part2
+      end
+   end
+
+   return false
+end
+
+-- Combine all stds and global related options into one final definition table.
+-- A definition table may have fields `read_only` (boolean), `other_fields` (boolean),
+-- and `fields` (maps field names to definition tables).
+-- Std table format is similar, except at the top level there are two fields
+-- `globals` and `read_globals` mapping to top-level field tables. Also in field tables
+-- it's possible to use field names in array part as a shortcut:
+-- `{fields = {"foo"}}` is equivalent to `{fields = {foo = {}}}` or `{fields = {foo = {other_fields = true}}}`
+-- in top level fields tables.
+local function get_final_std(opts_stack, stds)
+   local final_std = {}
+   local std_tables = get_std_tables(opts_stack, stds)
+
+   for _, std_table in ipairs(std_tables) do
+      standards.add_std_table(final_std, std_table)
+   end
+
+   local last_new_globals = index_of_last_option_usage(opts_stack, "new_globals")
+   local last_new_read_globals = index_of_last_option_usage(opts_stack, "new_read_globals")
+
+   for index, opts in ipairs(opts_stack) do
+      local globals = (index >= last_new_globals) and (opts.new_globals or opts.globals)
+      local read_globals = (index >= last_new_read_globals) and (opts.new_read_globals or opts.read_globals)
+
+      local new_fields = {}
+
+      if globals then
+         for _, global in ipairs(globals) do
+            table.insert(new_fields, {split_field(global), false})
+         end
+      end
+
+      if read_globals then
+         for _, read_global in ipairs(read_globals) do
+            table.insert(new_fields, {split_field(read_global), true})
+         end
+      end
+
+      if globals and read_globals then
+         -- If there are both globals and read-only globals defined in one options table,
+         -- it's important that more general definitions are applied first,
+         -- otherwise they will completely overwrite more specific definitions.
+         -- E.g. `globals x` should be applied before `read globals x.y`.
+         table.sort(new_fields, field_comparator)
+      end
+
+      for _, field in ipairs(new_fields) do
+         standards.overwrite_field(final_std, field[1], field[2])
+      end
+
+      standards.add_std_table(final_std, {globals = globals, read_globals = read_globals}, true, true)
+
+      if opts.not_globals then
+         for _, not_global in ipairs(opts.not_globals) do
+            standards.remove_field(final_std, split_field(not_global))
+         end
+      end
+   end
+
+   standards.finalize(final_std)
+   return final_std
+end
+
+local function get_scalar_opt(opts_stack, option, default)
+   for _, opts in utils.ripairs(opts_stack) do
+      if opts[option] ~= nil then
+         return opts[option]
+      end
+   end
+
+   return default
+end
+
+local line_length_suboptions = {"max_code_line_length", "max_string_line_length", "max_comment_line_length"}
+
+local function get_max_line_opts(opts_stack)
+   local res = {max_line_length = 120}
+
+   for _, opt_name in ipairs(line_length_suboptions) do
+      res[opt_name] = res.max_line_length
+   end
+
+   for _, opts in ipairs(opts_stack) do
+      if opts.max_line_length ~= nil then
+         res.max_line_length = opts.max_line_length
+
+         for _, opt_name in ipairs(line_length_suboptions) do
+            res[opt_name] = opts.max_line_length
+         end
+      end
+
+      for _, opt_name in ipairs(line_length_suboptions) do
+         if opts[opt_name] ~= nil then
+            res[opt_name] = opts[opt_name]
+         end
+      end
+   end
+
+   return res
+end
+
+local function anchor_pattern(pattern, only_start)
+   if not pattern then
+      return
+   end
+
+   if pattern:sub(1, 1) == "^" or pattern:sub(-1) == "$" then
+      return pattern
+   else
+      return "^" .. pattern .. (only_start and "" or "$")
+   end
+end
+
+-- Returns {pair of normalized patterns for code and name}.
+-- `pattern` can be:
+--    string containing '/': first part matches warning code, second - variable name;
+--    string containing letters: matches variable name;
+--    otherwise: matches warning code.
+-- Unless anchored by user, pattern for name is anchored from both sides
+-- and pattern for code is only anchored at the beginning.
+local function normalize_pattern(pattern)
+   local code_pattern, name_pattern
+   local slash_pos = pattern:find("/")
+
+   if slash_pos then
+      code_pattern = pattern:sub(1, slash_pos - 1)
+      name_pattern = pattern:sub(slash_pos + 1)
+   elseif pattern:find("[_a-zA-Z]") then
+      name_pattern = pattern
+   else
+      code_pattern = pattern
+   end
+
+   return {anchor_pattern(code_pattern, true), anchor_pattern(name_pattern)}
+end
+
+-- From most specific to less specific, pairs {option, pattern}.
+-- Applying macros in order is required to get deterministic results
+-- and get sensible results when intersecting macros are used.
+-- E.g. unused = false, unused_args = true should leave unused args enabled.
+local macros = {
+   {"unused_args", "21[23]"},
+   {"global", "1"},
+   {"unused", "[23]"},
+   {"redefined", "4"}
+}
+
+-- Returns array of rules which should be applied in order.
+-- A rule is a table {{pattern*}, type}.
+-- `pattern` is a non-normalized pattern.
+-- `type` can be "enable", "disable" or "only".
+local function get_rules(opts_stack)
+   local rules = {}
+   local used_macros = {}
+
+   for _, opts in utils.ripairs(opts_stack) do
+      for _, macro_info in ipairs(macros) do
+         local option, pattern = macro_info[1], macro_info[2]
+
+         if not used_macros[option] then
+            if opts[option] ~= nil then
+               table.insert(rules, {{pattern}, opts[option] and "enable" or "disable"})
+               used_macros[option] = true
+            end
+         end
+      end
+
+      if opts.ignore then
+         table.insert(rules, {opts.ignore, "disable"})
+      end
+
+      if opts.only then
+         table.insert(rules, {opts.only, "only"})
+      end
+
+      if opts.enable then
+         table.insert(rules, {opts.enable, "enable"})
+      end
+   end
+
+   return rules
+end
+
+local function normalize_patterns(rules)
+   local res = {}
+
+   for i, rule in ipairs(rules) do
+      res[i] = {{}, rule[2]}
+
+      for j, pattern in ipairs(rule[1]) do
+         res[i][1][j] = normalize_pattern(pattern)
+      end
+   end
+
+   return res
+end
+
+local scalar_options = {
+   unused_secondaries = true,
+   self = true,
+   module = false,
+   allow_defined = false,
+   allow_defined_top = false,
+   max_cyclomatic_complexity = false
+}
+
+-- Returns normalized options.
+-- Normalized options have fields:
+--    std: normalized std table, see `luacheck.standards` module;
+--    unused_secondaries, self, module, allow_defined, allow_defined_top: booleans;
+--    max_line_length: number or false;
+--    rules: see get_rules.
+function options.normalize(opts_stack, stds)
+   local res = {}
+   stds = stds or builtin_standards
+   res.std = get_final_std(opts_stack, stds)
+
+   for option, default in pairs(scalar_options) do
+      res[option] = get_scalar_opt(opts_stack, option, default)
+   end
+
+   local max_line_opts = get_max_line_opts(opts_stack)
+   utils.update(res, max_line_opts)
+   res.rules = normalize_patterns(get_rules(opts_stack))
+
+   return res
+end
+
+return options
diff --git a/script/luacheck/parser.lua b/script/luacheck/parser.lua
new file mode 100755
index 000000000..744684156
--- /dev/null
+++ b/script/luacheck/parser.lua
@@ -0,0 +1,984 @@
+local lexer = require "luacheck.lexer"
+local utils = require "luacheck.utils"
+
+local parser = {}
+
+-- A table with range info, or simply range, has `line`, `offset`, and `end_offset` fields.
+-- `line` is the line of the first character.
+-- Parser state table has range info for the current token, and all AST
+-- node tables have range info for themself, including parens around expressions
+-- that are otherwise not reflected in the AST structure.
+
+parser.SyntaxError = utils.class()
+
+function parser.SyntaxError:__init(msg, range, prev_range)
+   self.msg = msg
+   self.line = range.line
+   self.offset = range.offset
+   self.end_offset = range.end_offset
+
+   if prev_range then
+      self.prev_line = prev_range.line
+      self.prev_offset = prev_range.offset
+      self.prev_end_offset = prev_range.end_offset
+   end
+end
+
+function parser.syntax_error(msg, range, prev_range)
+   error(parser.SyntaxError(msg, range, prev_range), 0)
+end
+
+local function mark_line_endings(state, token_type)
+   for line = state.line, state.lexer.line - 1 do
+      state.line_endings[line] = token_type
+   end
+end
+
+local function skip_token(state)
+   while true do
+      local token, token_value, line, offset, error_end_offset = lexer.next_token(state.lexer)
+      state.token = token
+      state.token_value = token_value
+      state.line = line
+      state.offset = offset
+      state.end_offset = error_end_offset or (state.lexer.offset - 1)
+
+      if not token then
+         parser.syntax_error(token_value, state)
+      end
+
+      if token == "short_comment" then
+         state.comments[#state.comments + 1] = {
+            contents = token_value,
+            line = line,
+            offset = offset,
+            end_offset = state.end_offset
+         }
+
+         state.line_endings[line] = "comment"
+      elseif token == "long_comment" then
+         mark_line_endings(state, "comment")
+      else
+         if token ~= "eof" then
+            mark_line_endings(state, "string")
+            state.code_lines[line] = true
+            state.code_lines[state.lexer.line] = true
+         end
+
+         return
+      end
+   end
+end
+
+local function token_name(token)
+   return token == "name" and "identifier" or (token == "eof" and "<eof>" or ("'" .. token .. "'"))
+end
+
+local function parse_error(state, msg, prev_range, token_prefix, message_suffix)
+   local token_repr
+
+   if state.token == "eof" then
+      token_repr = "<eof>"
+   else
+      token_repr = lexer.get_quoted_substring_or_line(state.lexer, state.line, state.offset, state.end_offset)
+   end
+
+   if token_prefix then
+      token_repr = token_prefix .. " " .. token_repr
+   end
+
+   msg = msg .. " near " .. token_repr
+
+   if message_suffix then
+      msg = msg .. " " .. message_suffix
+   end
+
+   parser.syntax_error(msg, state, prev_range)
+end
+
+local function check_token(state, token)
+   if state.token ~= token then
+      parse_error(state, "expected " .. token_name(token))
+   end
+end
+
+local function check_and_skip_token(state, token)
+   check_token(state, token)
+   skip_token(state)
+end
+
+local function test_and_skip_token(state, token)
+   if state.token == token then
+      skip_token(state)
+      return true
+   end
+end
+
+local function copy_range(range)
+   return {
+      line = range.line,
+      offset = range.offset,
+      end_offset = range.end_offset
+   }
+end
+
+local new_state
+local parse_block
+local missing_closing_token_error
+
+-- Attempt to guess a better location for missing `end` and `until` errors (usually they uselessly point to eof).
+-- Guessed error token should be selected in such a way that inserting previously missing closing token
+-- in front of it should fix the error or at least move its opening token forward.
+-- The idea is to track the stack of opening tokens and their indentations.
+-- For the first statement or closing token with the same or smaller indentation than the opening token
+-- on the top of the stack:
+-- * If it has the same indentation but is not the appropriate closing token for the opening one, pick it
+--   as the guessed error location.
+-- * If it has a lower indentation level, pick it as the guessed error location even it closes the opening token.
+-- Example:
+-- local function f()
+--    <code>
+--
+--    if cond then                   <- `if` is the guessed opening token
+--       <code>
+--
+--    <code not starting with `end`> <- first token on this line is the guessed error location
+-- end
+-- Another one:
+-- local function g()
+--    <code>
+--
+--    if cond then  <- `if` is the guessed opening token
+--       <code>
+--
+-- end              <- `end` is the guessed error location
+
+local opening_token_to_closing = {
+   ["("] = ")",
+   ["["] = "]",
+   ["{"] = "}",
+   ["do"] = "end",
+   ["if"] = "end",
+   ["else"] = "end",
+   ["elseif"] = "end",
+   ["while"] = "end",
+   ["repeat"] = "until",
+   ["for"] = "end",
+   ["function"] = "end"
+}
+
+local function get_indentation(state, line)
+   local ws_start, ws_end = state.lexer.src:find("^[ \t\v\f]*", state.lexer.line_offsets[line])
+   return ws_end - ws_start
+end
+
+local UnpairedTokenGuesser = utils.class()
+
+function UnpairedTokenGuesser:__init(state, error_opening_range, error_closing_token)
+   self.old_state = state
+   self.error_offset = state.offset
+   self.error_opening_range = error_opening_range
+   self.error_closing_token = error_closing_token
+   self.opening_tokens_stack = utils.Stack()
+end
+
+function UnpairedTokenGuesser:guess()
+   -- Need to reinitialize lexer (e.g. to skip shebang again).
+   self.state = new_state(self.old_state.lexer.src)
+   self.state.unpaired_token_guesser = self
+   skip_token(self.state)
+   parse_block(self.state)
+   error("No syntax error in second parse", 0)
+end
+
+function UnpairedTokenGuesser:on_block_start(opening_token_range, opening_token)
+   local token_wrapper = copy_range(opening_token_range)
+   token_wrapper.token = opening_token
+   token_wrapper.closing_token = opening_token_to_closing[opening_token]
+   token_wrapper.eligible = token_wrapper.closing_token == self.error_closing_token
+   token_wrapper.indentation = get_indentation(self.state, opening_token_range.line)
+   self.opening_tokens_stack:push(token_wrapper)
+end
+
+function UnpairedTokenGuesser:set_guessed()
+   -- Keep the first detected location.
+   if self.guessed then
+      return
+   end
+
+   self.guessed = self.opening_tokens_stack.top
+   self.guessed.error_token = self.state.token
+   self.guessed.error_range = copy_range(self.state)
+end
+
+function UnpairedTokenGuesser:check_token()
+   local top = self.opening_tokens_stack.top
+
+   if top and top.eligible and self.state.line > top.line then
+      local token_indentation = get_indentation(self.state, self.state.line)
+
+      if token_indentation < top.indentation then
+         self:set_guessed()
+      elseif token_indentation == top.indentation then
+         local token = self.state.token
+
+         if token ~= top.closing_token and
+               ((top.token ~= "if" and top.token ~= "elseif") or (token ~= "elseif" and token ~= "else")) then
+            self:set_guessed()
+         end
+      end
+   end
+
+   if self.state.offset == self.error_offset then
+      if self.guessed and self.guessed.error_range.offset ~= self.state.offset then
+         self.state.line = self.guessed.error_range.line
+         self.state.offset = self.guessed.error_range.offset
+         self.state.end_offset = self.guessed.error_range.end_offset
+         self.state.token = self.guessed.error_token
+         missing_closing_token_error(self.state, self.guessed, self.guessed.token, self.guessed.closing_token, true)
+      end
+   end
+end
+
+function UnpairedTokenGuesser:on_block_end()
+   self:check_token()
+   self.opening_tokens_stack:pop()
+
+   if not self.opening_tokens_stack.top then
+      -- Inserting an end token into a balanced sequence of tokens adds an error earlier than original one.
+      self.guessed = nil
+   end
+end
+
+function UnpairedTokenGuesser:on_statement()
+   self:check_token()
+end
+
+function missing_closing_token_error(state, opening_range, opening_token, closing_token, is_guess)
+   local msg = "expected " .. token_name(closing_token)
+
+   if opening_range and opening_range.line ~= state.line then
+      msg = msg .. " (to close " .. token_name(opening_token) .. " on line " .. tostring(opening_range.line) .. ")"
+   end
+
+   local token_prefix
+   local message_suffix
+
+   if is_guess then
+      if state.token == closing_token then
+         -- "expected 'end' near 'end'" seems confusing.
+         token_prefix = "less indented"
+      end
+
+      message_suffix = "(indentation-based guess)"
+   end
+
+   parse_error(state, msg, opening_range, token_prefix, message_suffix)
+end
+
+local function check_closing_token(state, opening_range, opening_token)
+   local closing_token = opening_token_to_closing[opening_token] or "eof"
+
+   if state.token == closing_token then
+      return
+   end
+
+   if (opening_token == "if" or opening_token == "elseif") and (state.token == "else" or state.token == "elseif") then
+      return
+   end
+
+   if closing_token == "end" or closing_token == "until" then
+      if not state.unpaired_token_guesser then
+         UnpairedTokenGuesser(state, opening_range, closing_token):guess()
+      end
+   end
+
+   missing_closing_token_error(state, opening_range, opening_token, closing_token)
+end
+
+local function check_and_skip_closing_token(state, opening_range, opening_token)
+   check_closing_token(state, opening_range, opening_token)
+   skip_token(state)
+end
+
+local function check_name(state)
+   check_token(state, "name")
+   return state.token_value
+end
+
+local function new_outer_node(range, tag, node)
+   node = node or {}
+   node.line = range.line
+   node.offset = range.offset
+   node.end_offset = range.end_offset
+   node.tag = tag
+   return node
+end
+
+local function new_inner_node(start_range, end_range, tag, node)
+   node = node or {}
+   node.line = start_range.line
+   node.offset = start_range.offset
+   node.end_offset = end_range.end_offset
+   node.tag = tag
+   return node
+end
+
+local parse_expression
+
+local function parse_expression_list(state, list)
+   list = list or {}
+
+   repeat
+      list[#list + 1] = parse_expression(state)
+   until not test_and_skip_token(state, ",")
+
+   return list
+end
+
+local function parse_id(state, tag)
+   local ast_node = new_outer_node(state, tag or "Id")
+   ast_node[1] = check_name(state)
+   -- Skip name.
+   skip_token(state)
+   return ast_node
+end
+
+local function atom(tag)
+   return function(state)
+      local ast_node = new_outer_node(state, tag)
+      ast_node[1] = state.token_value
+      skip_token(state)
+      return ast_node
+   end
+end
+
+local simple_expressions = {}
+
+simple_expressions.number = atom("Number")
+simple_expressions.string = atom("String")
+simple_expressions["nil"] = atom("Nil")
+simple_expressions["true"] = atom("True")
+simple_expressions["false"] = atom("False")
+simple_expressions["..."] = atom("Dots")
+
+simple_expressions["{"] = function(state)
+   local start_range = copy_range(state)
+   local ast_node = {}
+   skip_token(state)
+
+   repeat
+      if state.token == "}" then
+         break
+      end
+
+      local key_node, value_node
+      local first_token_range = copy_range(state)
+
+      if state.token == "name" then
+         local name = state.token_value
+         skip_token(state)  -- Skip name.
+
+         if test_and_skip_token(state, "=") then
+            -- `name` = `expr`.
+            key_node = new_outer_node(first_token_range, "String", {name})
+            value_node = parse_expression(state)
+         else
+            -- `name` is beginning of an expression in array part.
+            -- Backtrack lexer to before name.
+            state.lexer.line = first_token_range.line
+            state.lexer.offset = first_token_range.offset
+            skip_token(state)  -- Load name again.
+            value_node = parse_expression(state)
+         end
+      elseif state.token == "[" then
+         -- [ `expr` ] = `expr`.
+         skip_token(state)
+         key_node = parse_expression(state)
+         check_and_skip_closing_token(state, first_token_range, "[")
+         check_and_skip_token(state, "=")
+         value_node = parse_expression(state)
+      else
+         -- Expression in array part.
+         value_node = parse_expression(state)
+      end
+
+      if key_node then
+         -- Pair.
+         ast_node[#ast_node + 1] = new_inner_node(first_token_range, value_node, "Pair", {key_node, value_node})
+      else
+         -- Array part item.
+         ast_node[#ast_node + 1] = value_node
+      end
+   until not (test_and_skip_token(state, ",") or test_and_skip_token(state, ";"))
+
+   new_inner_node(start_range, state, "Table", ast_node)
+   check_and_skip_closing_token(state, start_range, "{")
+   return ast_node
+end
+
+-- Parses argument list and the statements.
+local function parse_function(state, function_range)
+   local paren_range = copy_range(state)
+   check_and_skip_token(state, "(")
+   local args = {}
+
+   -- Are there arguments?
+   if state.token ~= ")" then
+      repeat
+         if state.token == "name" then
+            args[#args + 1] = parse_id(state)
+         elseif state.token == "..." then
+            args[#args + 1] = simple_expressions["..."](state)
+            break
+         else
+            parse_error(state, "expected argument")
+         end
+      until not test_and_skip_token(state, ",")
+   end
+
+   check_and_skip_closing_token(state, paren_range, "(")
+   local body = parse_block(state, function_range, "function")
+   local end_range = copy_range(state)
+   -- Skip "function".
+   skip_token(state)
+   return new_inner_node(function_range, end_range, "Function", {args, body, end_range = end_range})
+end
+
+simple_expressions["function"] = function(state)
+   local function_range = copy_range(state)
+   -- Skip "function".
+   skip_token(state)
+   return parse_function(state, function_range)
+end
+
+-- A call handler parses arguments of a call with given base node that determines resulting node start location,
+-- given tag, and array to which the arguments should be appended.
+local call_handlers = {}
+
+call_handlers["("] = function(state, base_node, tag, node)
+   local paren_range = copy_range(state)
+   -- Skip "(".
+   skip_token(state)
+
+   if state.token ~= ")" then
+      parse_expression_list(state, node)
+   end
+
+   new_inner_node(base_node, state, tag, node)
+   check_and_skip_closing_token(state, paren_range, "(")
+   return node
+end
+
+call_handlers["{"] = function(state, base_node, tag, node)
+   local arg_node = simple_expressions[state.token](state)
+   node[#node + 1] = arg_node
+   return new_inner_node(base_node, arg_node, tag, node)
+end
+
+call_handlers.string = call_handlers["{"]
+
+local suffix_handlers = {}
+
+suffix_handlers["."] = function(state, base_node)
+   -- Skip ".".
+   skip_token(state)
+   local index_node = parse_id(state, "String")
+   return new_inner_node(base_node, index_node, "Index", {base_node, index_node})
+end
+
+suffix_handlers["["] = function(state, base_node)
+   local bracket_range = copy_range(state)
+   -- Skip "[".
+   skip_token(state)
+   local index_node = parse_expression(state)
+   local ast_node = new_inner_node(base_node, state, "Index", {base_node, index_node})
+   check_and_skip_closing_token(state, bracket_range, "[")
+   return ast_node
+end
+
+suffix_handlers[":"] = function(state, base_node)
+   -- Skip ":".
+   skip_token(state)
+   local method_name = parse_id(state, "String")
+   local call_handler = call_handlers[state.token]
+
+   if not call_handler then
+      parse_error(state, "expected method arguments")
+   end
+
+   return call_handler(state, base_node, "Invoke", {base_node, method_name})
+end
+
+suffix_handlers["("] = function(state, base_node)
+   return call_handlers[state.token](state, base_node, "Call", {base_node})
+end
+
+suffix_handlers["{"] = suffix_handlers["("]
+suffix_handlers.string = suffix_handlers["("]
+
+local function parse_simple_expression(state, kind, no_literals)
+   local expression
+
+   if state.token == "(" then
+      local paren_range = copy_range(state)
+      skip_token(state)
+      local inner_expression = parse_expression(state)
+      expression = new_inner_node(paren_range, state, "Paren", {inner_expression})
+      check_and_skip_closing_token(state, paren_range, "(")
+   elseif state.token == "name" then
+      expression = parse_id(state)
+   else
+      local literal_handler = simple_expressions[state.token]
+
+      if not literal_handler or no_literals then
+         parse_error(state, "expected " .. (kind or "expression"))
+      end
+
+      return literal_handler(state)
+   end
+
+   while true do
+      local suffix_handler = suffix_handlers[state.token]
+
+      if suffix_handler then
+         expression = suffix_handler(state, expression)
+      else
+         return expression
+      end
+   end
+end
+
+local unary_operators = {
+   ["not"] = "not",
+   ["-"] = "unm",
+   ["~"] = "bnot",
+   ["#"] = "len"
+}
+
+local unary_priority = 12
+
+local binary_operators = {
+   ["+"] = "add", ["-"] = "sub",
+   ["*"] = "mul", ["%"] = "mod",
+   ["^"] = "pow",
+   ["/"] = "div", ["//"] = "idiv",
+   ["&"] = "band", ["|"] = "bor", ["~"] = "bxor",
+   ["<<"] = "shl", [">>"] = "shr",
+   [".."] = "concat",
+   ["~="] = "ne", ["=="] = "eq",
+   ["<"] = "lt", ["<="] = "le",
+   [">"] = "gt", [">="] = "ge",
+   ["and"] = "and", ["or"] = "or"
+}
+
+local left_priorities = {
+   add = 10, sub = 10,
+   mul = 11, mod = 11,
+   pow = 14,
+   div = 11, idiv = 11,
+   band = 6, bor = 4, bxor = 5,
+   shl = 7, shr = 7,
+   concat = 9,
+   ne = 3, eq = 3,
+   lt = 3, le = 3,
+   gt = 3, ge = 3,
+   ["and"] = 2, ["or"] = 1
+}
+
+local right_priorities = {
+   add = 10, sub = 10,
+   mul = 11, mod = 11,
+   pow = 13,
+   div = 11, idiv = 11,
+   band = 6, bor = 4, bxor = 5,
+   shl = 7, shr = 7,
+   concat = 8,
+   ne = 3, eq = 3,
+   lt = 3, le = 3,
+   gt = 3, ge = 3,
+   ["and"] = 2, ["or"] = 1
+}
+
+local function parse_subexpression(state, limit, kind)
+   local expression
+   local unary_operator = unary_operators[state.token]
+
+   if unary_operator then
+      local operator_range = copy_range(state)
+      -- Skip operator.
+      skip_token(state)
+      local operand = parse_subexpression(state, unary_priority)
+      expression = new_inner_node(operator_range, operand, "Op", {unary_operator, operand})
+   else
+      expression = parse_simple_expression(state, kind)
+   end
+
+   -- Expand while operators have priorities higher than `limit`.
+   while true do
+      local binary_operator = binary_operators[state.token]
+
+      if not binary_operator or left_priorities[binary_operator] <= limit then
+         break
+      end
+
+       -- Skip operator.
+      skip_token(state)
+      -- Read subexpression with higher priority.
+      local subexpression = parse_subexpression(state, right_priorities[binary_operator])
+      expression = new_inner_node(expression, subexpression, "Op", {binary_operator, expression, subexpression})
+   end
+
+   return expression
+end
+
+function parse_expression(state, kind)
+   return parse_subexpression(state, 0, kind)
+end
+
+local statements = {}
+
+statements["if"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "if".
+   skip_token(state)
+   local ast_node = {}
+
+   -- The loop is entered after skipping "if" or "elseif".
+   -- Block start token info is set to the last skipped "if", "elseif", or "else" token.
+   local block_start_token = "if"
+   local block_start_range = start_range
+
+   while true do
+      ast_node[#ast_node + 1] = parse_expression(state, "condition")
+      -- Add range of the "then" token to the block statement array.
+      local branch_range = copy_range(state)
+      check_and_skip_token(state, "then")
+      ast_node[#ast_node + 1] = parse_block(state, block_start_range, block_start_token, branch_range)
+
+      if state.token == "else" then
+         branch_range = copy_range(state)
+         block_start_token = "else"
+         block_start_range = branch_range
+         skip_token(state)
+         ast_node[#ast_node + 1] = parse_block(state, block_start_range, block_start_token, branch_range)
+         break
+      elseif state.token == "elseif" then
+         block_start_token = "elseif"
+         block_start_range = copy_range(state)
+         skip_token(state)
+      else
+         break
+      end
+   end
+
+   new_inner_node(start_range, state, "If", ast_node)
+   -- Skip "end".
+   skip_token(state)
+   return ast_node
+end
+
+statements["while"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "while".
+   skip_token(state)
+   local condition = parse_expression(state, "condition")
+   check_and_skip_token(state, "do")
+   local block = parse_block(state, start_range, "while")
+   local ast_node = new_inner_node(start_range, state, "While", {condition, block})
+   -- Skip "end".
+   skip_token(state)
+   return ast_node
+end
+
+statements["do"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "do".
+   skip_token(state)
+   local block = parse_block(state, start_range, "do")
+   local ast_node = new_inner_node(start_range, state, "Do", block)
+   -- Skip "end".
+   skip_token(state)
+   return ast_node
+end
+
+statements["for"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "for".
+   skip_token(state)
+
+   local ast_node = {}
+   local tag
+   local first_var = parse_id(state)
+
+   if state.token == "=" then
+      -- Numeric "for" loop.
+      tag = "Fornum"
+      -- Skip "=".
+      skip_token(state)
+      ast_node[1] = first_var
+      ast_node[2] = parse_expression(state)
+      check_and_skip_token(state, ",")
+      ast_node[3] = parse_expression(state)
+
+      if test_and_skip_token(state, ",") then
+         ast_node[4] = parse_expression(state)
+      end
+
+      check_and_skip_token(state, "do")
+      ast_node[#ast_node + 1] = parse_block(state, start_range, "for")
+   elseif state.token == "," or state.token == "in" then
+      -- Generic "for" loop.
+      tag = "Forin"
+
+      local iter_vars = {first_var}
+      while test_and_skip_token(state, ",") do
+         iter_vars[#iter_vars + 1] = parse_id(state)
+      end
+
+      ast_node[1] = iter_vars
+      check_and_skip_token(state, "in")
+      ast_node[2] = parse_expression_list(state)
+      check_and_skip_token(state, "do")
+      ast_node[3] = parse_block(state, start_range, "for")
+   else
+      parse_error(state, "expected '=', ',' or 'in'")
+   end
+
+   new_inner_node(start_range, state, tag, ast_node)
+   -- Skip "end".
+   skip_token(state)
+   return ast_node
+end
+
+statements["repeat"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "repeat".
+   skip_token(state)
+   local block = parse_block(state, start_range, "repeat")
+   -- Skip "until".
+   skip_token(state)
+   local condition = parse_expression(state, "condition")
+   return new_inner_node(start_range, condition, "Repeat", {block, condition})
+end
+
+statements["function"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "function".
+   skip_token(state)
+   local lhs = parse_id(state)
+   local implicit_self_range
+
+   while (not implicit_self_range) and (state.token == "." or state.token == ":") do
+      implicit_self_range = (state.token == ":") and copy_range(state)
+      -- Skip "." or ":".
+      skip_token(state)
+      local index_node = parse_id(state, "String")
+      lhs = new_inner_node(lhs, index_node, "Index", {lhs, index_node})
+   end
+
+   local function_node = parse_function(state, start_range)
+
+   if implicit_self_range then
+      -- Insert implicit "self" argument.
+      local self_arg = new_outer_node(implicit_self_range, "Id", {"self", implicit = true})
+      table.insert(function_node[1], 1, self_arg)
+   end
+
+   return new_inner_node(start_range, function_node, "Set", {{lhs}, {function_node}})
+end
+
+statements["local"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "local".
+   skip_token(state)
+
+   if state.token == "function" then
+      -- Local function.
+      local function_range = copy_range(state)
+      -- Skip "function".
+      skip_token(state)
+      local var = parse_id(state)
+      local function_node = parse_function(state, function_range)
+      return new_inner_node(start_range, function_node, "Localrec", {{var}, {function_node}})
+   end
+
+   -- Local definition, potentially with assignment.
+   local lhs = {}
+   local rhs
+
+   repeat
+      lhs[#lhs + 1] = parse_id(state)
+   until not test_and_skip_token(state, ",")
+
+   if test_and_skip_token(state, "=") then
+      rhs = parse_expression_list(state)
+   end
+
+   return new_inner_node(start_range, rhs and rhs[#rhs] or lhs[#lhs], "Local", {lhs, rhs})
+end
+
+statements["::"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "::".
+   skip_token(state)
+   local name = check_name(state)
+   -- Skip label name.
+   skip_token(state)
+   local ast_node = new_inner_node(start_range, state, "Label", {name})
+   check_and_skip_token(state, "::")
+   return ast_node
+end
+
+local closing_tokens = utils.array_to_set({"end", "eof", "else", "elseif", "until"})
+
+statements["return"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "return".
+   skip_token(state)
+
+   if closing_tokens[state.token] or state.token == ";" then
+      -- No return values.
+      return new_outer_node(start_range, "Return")
+   else
+      local returns = parse_expression_list(state)
+      return new_inner_node(start_range, returns[#returns], "Return", returns)
+   end
+end
+
+statements["break"] = function(state)
+   local ast_node = new_outer_node(state, "Break")
+   -- Skip "break".
+   skip_token(state)
+   return ast_node
+end
+
+statements["goto"] = function(state)
+   local start_range = copy_range(state)
+   -- Skip "goto".
+   skip_token(state)
+   local name = check_name(state)
+   local ast_node = new_outer_node(start_range, "Goto", {name})
+   -- Skip label name.
+   skip_token(state)
+   return ast_node
+end
+
+local function parse_expression_statement(state)
+   local lhs
+   local start_range = copy_range(state)
+
+   -- Handle lhs of an assignment or a single expression.
+   repeat
+      local item_start_range = lhs and copy_range(state) or start_range
+      local expected = lhs and "identifier or field" or "statement"
+      local primary_expression = parse_simple_expression(state, expected, true)
+
+      if primary_expression.tag == "Paren" then
+         -- (expr) in lhs is invalid.
+         parser.syntax_error("expected " .. expected .. " near '('", item_start_range)
+      end
+
+      if primary_expression.tag == "Call" or primary_expression.tag == "Invoke" then
+         if lhs then
+            -- The is an assingment, and a call is not valid in lhs.
+            parse_error(state, "expected call or indexing")
+         else
+            -- This is a call.
+            return primary_expression
+         end
+      end
+
+      -- This is an assignment.
+      lhs = lhs or {}
+      lhs[#lhs + 1] = primary_expression
+   until not test_and_skip_token(state, ",")
+
+   check_and_skip_token(state, "=")
+   local rhs = parse_expression_list(state)
+   return new_inner_node(start_range, rhs[#rhs], "Set", {lhs, rhs})
+end
+
+local function parse_statement(state)
+   return (statements[state.token] or parse_expression_statement)(state)
+end
+
+function parse_block(state, opening_token_range, opening_token, block)
+   local unpaired_token_guesser = state.unpaired_token_guesser
+
+   if unpaired_token_guesser and opening_token then
+      unpaired_token_guesser:on_block_start(opening_token_range, opening_token)
+   end
+
+   block = block or {}
+   local after_statement = false
+
+   while not closing_tokens[state.token] do
+      local first_token = state.token
+
+      if first_token == ";" then
+         if not after_statement then
+            table.insert(state.hanging_semicolons, copy_range(state))
+         end
+
+         -- Skip ";".
+         skip_token(state)
+         -- Further semicolons are considered hanging.
+         after_statement = false
+      else
+         if unpaired_token_guesser then
+            unpaired_token_guesser:on_statement()
+         end
+
+         local statement = parse_statement(state)
+         after_statement = true
+         block[#block + 1] = statement
+
+         if statement.tag == "Return" then
+            -- "return" must be the last statement.
+            -- However, one ";" after it is allowed.
+            test_and_skip_token(state, ";")
+            break
+         end
+      end
+   end
+
+   if unpaired_token_guesser and opening_token then
+      unpaired_token_guesser:on_block_end()
+   end
+
+   check_closing_token(state, opening_token_range, opening_token)
+
+   return block
+end
+
+function new_state(src, line_offsets, line_lengths)
+   return {
+      lexer = lexer.new_state(src, line_offsets, line_lengths),
+      -- Set of line numbers containing code.
+      code_lines = {},
+      -- Maps line numbers to "comment", "string", or nil based on whether the line ending is within a token
+      line_endings = {},
+      -- Array of {contents = string} with range info.
+      comments = {},
+       -- Array of ranges of semicolons not following a statement.
+      hanging_semicolons = {}
+   }
+end
+
+-- Parses source characters.
+-- Returns AST (in almost MetaLua format), array of comments - tables {contents = string} with range info,
+-- set of line numbers containing code, map of types of tokens wrapping line endings (nil, "string", or "comment"),
+-- array of ranges of hanging semicolons (not after statements), array of line start offsets, array of line lengths.
+-- The last two tables can be passed as arguments to be filled.
+-- On error throws an instance of parser.SyntaxError: table {msg = msg, prev_range = prev_range?} with range info,
+-- prev_range may refer to some extra relevant location.
+function parser.parse(src, line_offsets, line_lengths)
+   local state = new_state(src, line_offsets, line_lengths)
+   skip_token(state)
+   local ast = parse_block(state)
+   return ast, state.comments, state.code_lines, state.line_endings, state.hanging_semicolons,
+      state.lexer.line_offsets, state.lexer.line_lengths
+end
+
+return parser
diff --git a/script/luacheck/profiler.lua b/script/luacheck/profiler.lua
new file mode 100755
index 000000000..37104a799
--- /dev/null
+++ b/script/luacheck/profiler.lua
@@ -0,0 +1,149 @@
+-- Require luasocket only when needed.
+local socket
+
+local profiler = {}
+
+local metrics = {
+   {name = "Wall", get = function() return socket.gettime() end},
+   {name = "CPU", get = os.clock},
+   {name = "Memory", get = function() return collectgarbage("count") end}
+}
+
+local functions = {
+   {name = "sha1", module = "vendor.sha1"},
+   {name = "load", module = "cache"},
+   {name = "update", module = "cache"},
+   {name = "decode", module = "decoder"},
+   {name = "parse", module = "parser"},
+   {name = "dump_check_result", module = "serializer"},
+   {name = "load_check_result", module = "serializer"},
+   {name = "run", module = "stages.unwrap_parens"},
+   {name = "run", module = "stages.parse_inline_options"},
+   {name = "run", module = "stages.linearize"},
+   {name = "run", module = "stages.name_functions"},
+   {name = "run", module = "stages.resolve_locals"},
+   {name = "run", module = "stages.detect_bad_whitespace"},
+   {name = "run", module = "stages.detect_cyclomatic_complexity"},
+   {name = "run", module = "stages.detect_empty_blocks"},
+   {name = "run", module = "stages.detect_empty_statements"},
+   {name = "run", module = "stages.detect_globals"},
+   {name = "run", module = "stages.detect_reversed_fornum_loops"},
+   {name = "run", module = "stages.detect_unbalanced_assignments"},
+   {name = "run", module = "stages.detect_uninit_accesses"},
+   {name = "run", module = "stages.detect_unreachable_code"},
+   {name = "run", module = "stages.detect_unused_fields"},
+   {name = "run", module = "stages.detect_unused_locals"},
+   {name = "filter", module = "filter"},
+   {name = "normalize", module = "options"}
+}
+
+local stats = {}
+local start_values = {}
+
+local function start_phase(name)
+   for _, metric in ipairs(metrics) do
+      start_values[metric][name] = metric.get()
+   end
+end
+
+local function stop_phase(name)
+   for _, metric in ipairs(metrics) do
+      local increment = metric.get() - start_values[metric][name]
+      stats[metric][name] = (stats[metric][name] or 0) + increment
+   end
+end
+
+local phase_stack = {}
+
+local function push_phase(name)
+   local prev_name = phase_stack[#phase_stack]
+
+   if prev_name then
+      stop_phase(prev_name)
+   end
+
+   table.insert(phase_stack, name)
+   start_phase(name)
+end
+
+local function pop_phase(name)
+   assert(name == table.remove(phase_stack))
+   stop_phase(name)
+   local prev_name = phase_stack[#phase_stack]
+
+   if prev_name then
+      start_phase(prev_name)
+   end
+end
+
+local function continue_wrapper(name, ...)
+   pop_phase(name)
+   return ...
+end
+
+local function wrap(fn, name)
+   return function(...)
+      push_phase(name)
+      return continue_wrapper(name, fn(...))
+   end
+end
+
+local function patch(fn)
+   local mod = require("luacheck." .. fn.module)
+   local orig = mod[fn.name]
+   local new = wrap(orig, fn.module .. "." .. fn.name)
+   mod[fn.name] = new
+end
+
+function profiler.init()
+   socket = require "socket"
+   collectgarbage("stop")
+
+   for _, metric in ipairs(metrics) do
+      stats[metric] = {}
+      start_values[metric] = {}
+   end
+
+   for _, fn in ipairs(functions) do
+      patch(fn)
+   end
+
+   push_phase("other")
+end
+
+function profiler.report()
+   pop_phase("other")
+
+   for _, metric in ipairs(metrics) do
+      local names = {}
+      local total = 0
+
+      for name, value in pairs(stats[metric]) do
+         table.insert(names, name)
+         total = total + value
+      end
+
+      table.sort(names, function(name1, name2)
+         local stats1 = stats[metric][name1]
+         local stats2 = stats[metric][name2]
+
+         if stats1 ~= stats2 then
+            return stats1 > stats2
+         else
+            return name1 < name2
+         end
+      end)
+
+      print(metric.name)
+      print()
+
+      for _, name in ipairs(names) do
+         print(("%s - %f (%f%%)"):format(name, stats[metric][name], stats[metric][name] / total * 100))
+      end
+
+      print(("Total - %f"):format(total))
+      print()
+   end
+end
+
+return profiler
diff --git a/script/luacheck/runner.lua b/script/luacheck/runner.lua
new file mode 100755
index 000000000..ce46221dd
--- /dev/null
+++ b/script/luacheck/runner.lua
@@ -0,0 +1,415 @@
+local cache = require "luacheck.cache"
+local config = require "luacheck.config"
+local expand_rockspec = require "luacheck.expand_rockspec"
+local format = require "luacheck.format"
+local fs = require "luacheck.fs"
+local globbing = require "luacheck.globbing"
+local luacheck = require "luacheck"
+local multithreading = require "luacheck.multithreading"
+local options = require "luacheck.options"
+local utils = require "luacheck.utils"
+
+local runner = {}
+
+local Runner = utils.class()
+
+function Runner:__init(config_stack)
+   self._config_stack = config_stack
+end
+
+local config_options = {
+   config = utils.has_type_or_false("string"),
+   default_config = utils.has_type_or_false("string")
+}
+
+function runner.new(opts)
+   local ok, err = options.validate(config_options, opts)
+
+   if not ok then
+      error(("bad argument #1 to 'runner.new' (%s)"):format(err))
+   end
+
+   local base_config, config_err = config.load_config(opts.config, opts.default_config)
+
+   if not base_config then
+      return nil, config_err
+   end
+
+   local override_config = config.table_to_config(opts)
+
+   local config_stack
+   config_stack, err = config.stack_configs({base_config, override_config})
+
+   if not config_stack then
+      return nil, err
+   end
+
+   return Runner(config_stack)
+end
+
+local function validate_inputs(inputs)
+   if type(inputs) ~= "table" then
+      return nil, ("inputs table expected, got %s"):format(inputs)
+   end
+
+   for index, input in ipairs(inputs) do
+      local context = ("invalid input table at index [%d]"):format(index)
+
+      if type(input) ~= "table" then
+         return nil, ("%s: table expected, got %s"):format(context, type(input))
+      end
+
+      local specifies_source
+
+      for _, field in ipairs({"file", "filename", "path", "rockspec_path", "string"}) do
+         if input[field] ~= nil then
+            if field == "file" then
+               if io.type(input[field]) ~= "file" then
+                  return nil, ("%s: invalid field 'file': open file expected, got %s"):format(
+                     context, type(input[field]))
+               end
+            elseif type(input[field]) ~= "string" then
+               return nil, ("%s: invalid field '%s': string expected, got %s"):format(
+                  context, field, type(input[field]))
+            end
+
+            if field ~= "filename" then
+               specifies_source = true
+            end
+         end
+      end
+
+      if not specifies_source then
+         return nil, ("%s: one of fields 'path', 'rockspec_path', 'file', or 'string' must be present"):format(context)
+      end
+   end
+
+   return true
+end
+
+local function matches_any(globs, filename)
+   for _, glob in ipairs(globs) do
+      if globbing.match(glob, filename) then
+         return true
+      end
+   end
+
+   return false
+end
+
+function Runner:_is_filename_included(abs_filename)
+   return not matches_any(self._top_opts.exclude_files, abs_filename) and (
+      #self._top_opts.include_files == 0 or matches_any(self._top_opts.include_files, abs_filename))
+end
+
+-- Normalizes inputs and filters inputs using `exclude_files` and `include_files` options.
+-- Returns an array of prepared input tables.
+-- Differences between normal and prepated inputs:
+-- * Prepared inputs can't have `rockspec_path` field.
+-- * Prepared inputs can't have `path` pointing to a directory (unless it has an error).
+-- * Prepared inputs have `filename` field if possible (copied from `path` if not given).
+-- * Prepared inputs that have `path` field also have `abs_path` field.
+-- * Prepared inputs can have `fatal` field if the input can't be checked. The value is error type as a string.
+--   `fatal` is always accompanied by an error message in `msg` field.
+function Runner:_prepare_inputs(inputs)
+   local current_dir = fs.get_current_dir()
+   local dir_pattern = #self._top_opts.include_files > 0 and "" or "%.lua$"
+
+   local res = {}
+
+   local function add(input)
+      if input.path then
+         -- TODO: get rid of this, adjust fs.extract_files to avoid leading `./` instead.
+         input.path = input.path:gsub("^%.[/\\]([^/])", "%1")
+         input.abs_path = fs.normalize(fs.join(current_dir, input.path))
+      end
+
+      local abs_filename
+
+      if input.filename then
+         abs_filename = fs.normalize(fs.join(current_dir, input.filename))
+      else
+         input.filename = input.path
+         abs_filename = input.abs_path
+      end
+
+      if not input.filename or self:_is_filename_included(abs_filename) then
+         table.insert(res, input)
+      end
+   end
+
+   for _, input in ipairs(inputs) do
+      if input.path then
+         if fs.is_dir(input.path) then
+            local filenames, err_map = fs.extract_files(input.path, dir_pattern)
+
+            for _, filename in ipairs(filenames) do
+               local err = err_map[filename]
+               if err then
+                  add({path = filename, fatal = "I/O", msg = err, filename = input.filename})
+               else
+                  add({path = filename, filename = input.filename})
+               end
+            end
+         else
+            add({path = input.path, filename = input.filename})
+         end
+      elseif input.rockspec_path then
+         local filenames, fatal, err = expand_rockspec(input.rockspec_path)
+
+         if filenames then
+            for _, filename in ipairs(filenames) do
+               add({path = filename, filename = input.filename})
+            end
+         else
+            add({path = input.rockspec_path, fatal = fatal, msg = err, filename = input.filename})
+         end
+      elseif input.file then
+         add({file = input.file, filename = input.filename})
+      elseif input.string then
+         add({string = input.string, filename = input.filename})
+      else
+         -- Validation should ensure this never happens.
+         error("input doesn't specify source to check")
+      end
+   end
+
+   return res
+end
+
+-- Loads cached reports for inputs with `path` field, assigns them to `cached_report` field.
+-- For each file on cache load error sets its `fatal` and `msg` fields.
+function Runner:_add_cached_reports(inputs)
+   for _, input in ipairs(inputs) do
+      if not input.fatal and input.path then
+         local report, err = self._cache:get(input.path)
+
+         if err then
+            input.fatal = "I/O"
+            input.msg = ("Couldn't load cache for %s from %s: malformed data"):format(
+               self._top_opts.cache, input.path)
+         else
+            input.cached_report = report
+         end
+      end
+   end
+end
+
+-- Adds report as `new_report` field to all inputs that don't have a fatal error or a cached report.
+-- Adds `fatal` and `msg` instead if there was an I/O error.
+function Runner:_add_new_reports(inputs)
+   local sources = {}
+   local original_indexes = {}
+
+   for index, input in ipairs(inputs) do
+      if not input.fatal and not input.cached_report then
+         if input.string then
+            table.insert(sources, input.string)
+            table.insert(original_indexes, index)
+         else
+            local source, err = utils.read_file(input.path or input.file)
+
+            if source then
+               table.insert(sources, source)
+               table.insert(original_indexes, index)
+            else
+               input.fatal = "I/O"
+               input.msg = err
+            end
+         end
+      end
+   end
+
+   local map = multithreading.has_lanes and multithreading.pmap or utils.map
+   local reports = map(luacheck.get_report, sources, self._top_opts.jobs)
+
+   for index, report in ipairs(reports) do
+      inputs[original_indexes[index]].new_report = report
+   end
+end
+
+-- Saves `new_report` for files eligible for caching to cache.
+-- Returns true on success or nil and an error message on failure.
+function Runner:_save_new_reports_to_cache(inputs)
+   for _, input in ipairs(inputs) do
+      if input.new_report and input.path then
+         local ok = self._cache:put(input.path, input.new_report)
+
+         if not ok then
+            return nil, ("Couldn't save cache for %s from %s: I/O error"):format(input.path, self._top_opts.cache)
+         end
+      end
+   end
+
+   return true
+end
+
+-- Inputs are prepared here, see `Runner:_prepare_inputs`.
+-- Returns an array of reports, one per input, possibly annotated with fields `fatal`, `msg`, and `filename`.
+-- On critical error returns nil and an error message.
+function Runner:_get_reports(inputs)
+   if self._top_opts.cache then
+      local err
+      self._cache, err = cache.new(self._top_opts.cache)
+
+      if not self._cache then
+         return nil, err
+      end
+
+      self:_add_cached_reports(inputs)
+   end
+
+   self:_add_new_reports(inputs)
+
+   if self._top_opts.cache then
+      local ok, err = self:_save_new_reports_to_cache(inputs)
+
+      if not ok then
+         return nil, err
+      end
+   end
+
+   local res = {}
+
+   for _, input in ipairs(inputs) do
+      local report = input.cached_report or input.new_report
+
+      if not report then
+         report = {fatal = input.fatal, msg = input.msg}
+      end
+
+      report.filename = input.filename
+      table.insert(res, report)
+   end
+
+   return res
+end
+
+function Runner:_get_final_report(reports)
+   local processing_options = {}
+
+   for index, report in ipairs(reports) do
+      if not report.fatal then
+         processing_options[index] = self._config_stack:get_options(report.filename)
+      end
+   end
+
+   local final_report = luacheck.process_reports(reports, processing_options, self._config_stack:get_stds())
+
+   -- `luacheck.process_reports` doesn't preserve `filename` fields, re-add them.
+   -- TODO: make it preserve them?
+   for index, report in ipairs(reports) do
+      final_report[index].filename = report.filename
+   end
+
+   return final_report
+end
+
+-- Inputs is an array of tables, each one specifies an input.
+-- Each input table must have one of the following fields:
+-- * `path`: string pointing to a file or directory to check. Checking directories requires LuaFileSystem,
+--   and recursively checks all files within the directory. If `include_files` option is not used,
+--   only files with `.lua` extensions within the directory are considered.
+-- * `rockspec_path`: string pointing to a rockspec, all files with `.lua` extension within its `build.modules`,
+--   `build.install.lua`, and `build.install.bin` tables are checked.
+-- * `file`: an open file object. It is read till EOF and closed, contents are checked.
+-- * `string`: Lua code to check as a string.
+-- Additionally, each input table can have `filename` field: a string used when applying `exclude_files`
+-- and `include_files` options to the input, and also when figuring out which per-path option overrides to use.
+-- By default, if `path` field is given, it is also used as `filename`, otherwise the input is considered unnamed.
+-- Unnamed files always pass `exclude_files` and `include_files` filters and don't have any per-path options applied.
+function Runner:check(inputs)
+   local ok, err = validate_inputs(inputs)
+
+   if not ok then
+      error(("bad argument #1 to 'Runner:check' (%s)"):format(err))
+   end
+
+   -- Path-related top options can depend on current directory.
+   -- Assume it can't somehow change during `:check` call.
+   self._top_opts = self._config_stack:get_top_options()
+
+   local prepared_inputs = self:_prepare_inputs(inputs)
+   local reports, reports_err = self:_get_reports(prepared_inputs)
+
+   if not reports then
+      return nil, reports_err
+   end
+
+   return self:_get_final_report(reports)
+end
+
+-- Formats given report (same format as returned by `Runner:check`).
+-- Optionally a table of options can be passed as `format_opts`,
+-- it can contain options `formatter`. `quiet`, `color`, `codes`, and `ranges`,
+-- with priority over options from initialization and config.
+-- Returns formatted report as a string. It always has a newline at the end unless it is empty.
+-- On error returns nil and an error message.
+function Runner:format(report, format_opts)
+   if type(report) ~= "table" then
+      error(("bad argument #1 to 'Runner:format' (report table expected, got %s"):format(type(report)))
+   end
+
+   local is_valid, err = options.validate(config.format_options, format_opts)
+
+   if not is_valid then
+      error(("bad argument #2 to 'Runner:format' (%s)"):format(err))
+   end
+
+   local top_opts = self._config_stack:get_top_options()
+   format_opts = format_opts or {}
+
+   local combined_opts = {}
+
+   for _, option in ipairs({"formatter", "quiet", "color", "codes", "ranges"}) do
+      combined_opts[option] = top_opts[option]
+
+      if format_opts[option] ~= nil then
+         combined_opts[option] = format_opts[option]
+      end
+   end
+
+   local filenames = {}
+
+   for _, file_report in ipairs(report) do
+      table.insert(filenames, file_report.filename or "<unnamed source>")
+   end
+
+   local output
+
+   if format.builtin_formatters[combined_opts.formatter] then
+      output = format.format(report, filenames, combined_opts)
+   else
+      local formatter_func = combined_opts.formatter
+
+      if type(combined_opts.formatter) == "string" then
+         local require_ok
+         local formatter_anchor_dir
+
+         if not format_opts.formatter then
+            formatter_anchor_dir = top_opts.formatter_anchor_dir
+         end
+
+         require_ok, formatter_func = config.relative_require(formatter_anchor_dir, combined_opts.formatter)
+
+         if not require_ok then
+            return nil, ("Couldn't load custom formatter '%s': %s"):format(combined_opts.formatter, formatter_func)
+         end
+      end
+
+      local ok
+      ok, output = pcall(formatter_func, report, filenames, combined_opts)
+
+      if not ok then
+         return nil, ("Couldn't run custom formatter '%s': %s"):format(tostring(combined_opts.formatter), output)
+      end
+   end
+
+   if #output > 0 and output:sub(-1) ~= "\n" then
+      output = output .. "\n"
+   end
+
+   return output
+end
+
+return runner
diff --git a/script/luacheck/serializer.lua b/script/luacheck/serializer.lua
new file mode 100755
index 000000000..03e50142a
--- /dev/null
+++ b/script/luacheck/serializer.lua
@@ -0,0 +1,211 @@
+local parse_inline_options = require "luacheck.stages.parse_inline_options"
+local stages = require "luacheck.stages"
+local utils = require "luacheck.utils"
+
+local serializer = {}
+
+local option_fields = {
+   "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined",
+   "unused_secondaries", "allow_defined", "allow_defined_top", "module",
+   "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals",
+   "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length",
+   "max_cyclomatic_complexity"
+}
+
+local function compress_table(t, fields)
+   local res = {}
+
+   for index, field in ipairs(fields) do
+      local value = t[field]
+
+      if value ~= nil then
+         if field == "options" then
+            value = compress_table(value, option_fields)
+         end
+
+         res[index] = value
+      end
+   end
+
+   return res
+end
+
+local function compress_tables(tables, per_code_fields)
+   local res = {}
+
+   for _, t in ipairs(tables) do
+      local fields = per_code_fields and stages.warnings[t.code].fields or parse_inline_options.inline_option_fields
+      table.insert(res, compress_table(t, fields))
+   end
+
+   return res
+end
+
+local function compress_result(result)
+   local res = {}
+   res[1] = compress_tables(result.warnings, true)
+   res[2] = compress_tables(result.inline_options)
+   res[3] = result.line_lengths
+   res[4] = result.line_endings
+   return res
+end
+
+local function decompress_table(t, fields)
+   local res = {}
+
+   for index, field in ipairs(fields) do
+      local value = t[index]
+
+      if value ~= nil then
+         if field == "options" then
+            value = decompress_table(value, option_fields)
+         end
+
+         res[field] = value
+      end
+   end
+
+   return res
+end
+
+local function decompress_tables(tables, per_code_fields)
+   local res = {}
+
+   for _, t in ipairs(tables) do
+      local fields
+
+      if per_code_fields then
+         fields = stages.warnings[t[1]].fields
+      else
+         fields = parse_inline_options.inline_option_fields
+      end
+
+      table.insert(res, decompress_table(t, fields))
+   end
+
+   return res
+end
+
+local function decompress_result(compressed)
+   local result = {}
+   result.warnings = decompress_tables(compressed[1], true)
+   result.inline_options = decompress_tables(compressed[2])
+   result.line_lengths = compressed[3]
+   result.line_endings = compressed[4]
+   return result
+end
+
+local function get_local_name(index)
+   return string.char(index + (index > 26 and 70 or 64))
+end
+
+local function max_n(t)
+   local res = 0
+
+   for k in pairs(t) do
+      res = math.max(res, k)
+   end
+
+   return res
+end
+
+-- Serializes a value into buffer.
+-- `strings` is a table mapping string values to where they first occured or to name of local
+-- variable used to represent it.
+-- Array part contains representations of values saved into locals.
+local function add_value(buffer, strings, value, level)
+   if type(value) == "string" then
+      local prev = strings[value]
+
+      if type(prev) == "string" then
+         -- There is a local with such value.
+         table.insert(buffer, prev)
+      elseif type(prev) == "number" and #strings < 52 then
+         -- Value is used second time, put it into a local.
+         table.insert(strings, ("%q"):format(value))
+         local local_name = get_local_name(#strings)
+         buffer[prev] = local_name
+         table.insert(buffer, local_name)
+         strings[value] = local_name
+      else
+         table.insert(buffer, ("%q"):format(value))
+         strings[value] = #buffer
+      end
+   elseif type(value) == "table" then
+      -- Level 1 has the result, level 2 has warning/inline option/line info arrays,
+      -- level 3 has warnings/inline option containers, level 4 has inline options.
+      local allow_sparse = level ~= 3
+      local nil_tail_start
+      local is_sparse
+      local put_one
+      table.insert(buffer, "{")
+
+      for index = 1, max_n(value) do
+         local item = value[index]
+
+         if item == nil then
+            is_sparse = allow_sparse
+            nil_tail_start = nil_tail_start or index
+         else
+            if put_one then
+               table.insert(buffer, ",")
+            end
+
+            if is_sparse then
+               table.insert(buffer, ("[%d]="):format(index))
+            elseif nil_tail_start then
+               for _ = nil_tail_start, index - 1 do
+                  table.insert(buffer, "nil,")
+               end
+
+               nil_tail_start = nil
+            end
+
+            add_value(buffer, strings, item, level + 1)
+            put_one = true
+         end
+      end
+
+      table.insert(buffer, "}")
+   else
+      table.insert(buffer, tostring(value))
+   end
+end
+
+-- Serializes check result, returns a string.
+function serializer.dump_check_result(result)
+   local strings = {}
+   local buffer = {"", "return "}
+   add_value(buffer, strings, compress_result(result), 1)
+
+   if strings[1] then
+      local names = {}
+
+      for index in ipairs(strings) do
+         table.insert(names, get_local_name(index))
+      end
+
+      buffer[1] = "local " .. table.concat(names, ",") .. "=" .. table.concat(strings, ",") .. ";"
+   end
+
+   return table.concat(buffer)
+end
+
+-- Loads check result from a string, returns check result table or nothing on error.
+function serializer.load_check_result(dumped)
+   local func = utils.load(dumped, {})
+
+   if not func then
+      return
+   end
+
+   local ok, compressed_result = pcall(func)
+
+   if not ok or type(compressed_result) ~= "table" then
+      return
+   end
+
+   return decompress_result(compressed_result)
+end
+
+return serializer
diff --git a/script/luacheck/stages/detect_bad_whitespace.lua b/script/luacheck/stages/detect_bad_whitespace.lua
new file mode 100755
index 000000000..98974d21b
--- /dev/null
+++ b/script/luacheck/stages/detect_bad_whitespace.lua
@@ -0,0 +1,76 @@
+local stage = {}
+
+stage.warnings = {
+   ["611"] = {message_format = "line contains only whitespace", fields = {}},
+   ["612"] = {message_format = "line contains trailing whitespace", fields = {}},
+   ["613"] = {message_format = "trailing whitespace in a string", fields = {}},
+   ["614"] = {message_format = "trailing whitespace in a comment", fields = {}},
+   ["621"] = {message_format = "inconsistent indentation (SPACE followed by TAB)", fields = {}}
+}
+
+function stage.run(chstate)
+   local num_lines = #chstate.line_offsets
+
+   for line_number = 1, num_lines do
+      local line_offset = chstate.line_offsets[line_number]
+      local line_length = chstate.line_lengths[line_number]
+
+      if line_length > 0 then
+         local trailing_ws_pattern
+
+         if line_number == num_lines then
+            trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]?$"
+         else
+            trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]"
+         end
+
+         local line_start_byte, _, trailing_ws_start_byte, line_end_byte = chstate.source:find(
+            trailing_ws_pattern, line_offset)
+
+         local trailing_ws_code
+
+         if trailing_ws_start_byte then
+
+            if trailing_ws_start_byte == line_start_byte then
+               -- Line contains only whitespace (thus never considered "code").
+               trailing_ws_code = "611"
+            elseif not chstate.line_endings[line_number] then
+               -- Trailing whitespace on code line or after long comment.
+               trailing_ws_code = "612"
+            elseif chstate.line_endings[line_number] == "string" then
+               -- Trailing whitespace embedded in a string literal.
+               trailing_ws_code = "613"
+            elseif chstate.line_endings[line_number] == "comment" then
+            -- Trailing whitespace at the end of a line comment or inside long comment.
+               trailing_ws_code = "614"
+            end
+
+            -- The difference between the start and the end of the warning range
+            -- is the same in bytes and in characters because whitespace characters are ASCII.
+            -- Can calculate one based on the three others.
+            local trailing_ws_end_byte = line_end_byte - 1
+            local trailing_ws_end_char = line_offset + line_length - 1
+            local trailing_ws_start_char = trailing_ws_end_char - (trailing_ws_end_byte - trailing_ws_start_byte)
+
+            chstate:warn(trailing_ws_code, line_number, trailing_ws_start_char, trailing_ws_end_char)
+         end
+
+         -- Don't look for inconsistent whitespace in pure whitespace lines.
+         if trailing_ws_code ~= "611" then
+            local leading_ws_start_byte, leading_ws_end_byte = chstate.source:find(
+               "^[ \t\f\v]- \t[ \t\f\v]*", line_offset)
+
+            if leading_ws_start_byte then
+               -- Inconsistent leading whitespace (SPACE followed by TAB).
+
+               -- Calculate warning end in characters using same logic as above.
+               local leading_ws_start_char = line_offset
+               local leading_ws_end_char = leading_ws_start_char + (leading_ws_end_byte - leading_ws_start_byte)
+               chstate:warn("621", line_number, line_offset, leading_ws_end_char)
+            end
+         end
+      end
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_cyclomatic_complexity.lua b/script/luacheck/stages/detect_cyclomatic_complexity.lua
new file mode 100755
index 000000000..22e5988fa
--- /dev/null
+++ b/script/luacheck/stages/detect_cyclomatic_complexity.lua
@@ -0,0 +1,159 @@
+local utils = require "luacheck.utils"
+
+local stage = {}
+
+local function cyclomatic_complexity_message_format(warning)
+   local template = "cyclomatic complexity of %s is too high ({complexity} > {max_complexity})"
+
+   local function_descr
+
+   if warning.function_type == "main_chunk" then
+      function_descr = "main chunk"
+   elseif warning.function_name then
+      function_descr = "{function_type} {function_name!}"
+   else
+      function_descr = "function"
+   end
+
+   return template:format(function_descr)
+end
+
+stage.warnings = {
+   ["561"] = {message_format = cyclomatic_complexity_message_format,
+      fields = {"complexity", "function_type", "function_name"}}
+}
+
+local function warn_cyclomatic_complexity(chstate, line, complexity)
+   if line == chstate.top_line then
+      chstate:warn("561", 1, 1, 1, {
+         complexity = complexity,
+         function_type = "main_chunk"
+      })
+   else
+      local node = line.node
+
+      chstate:warn_range("561", node, {
+         complexity = complexity,
+         function_type = node[1][1] and node[1][1].implicit and "method" or "function",
+         function_name = node.name
+      })
+   end
+end
+
+local CyclomaticComplexityMetric = utils.class()
+
+function CyclomaticComplexityMetric:incr_decisions(count)
+   self.count = self.count + count
+end
+
+function CyclomaticComplexityMetric:calc_expr(node)
+   if node.tag == "Op" and (node[1] == "and" or node[1] == "or") then
+      self:incr_decisions(1)
+   end
+
+   if node.tag ~= "Function" then
+      self:calc_exprs(node)
+   end
+end
+
+function CyclomaticComplexityMetric:calc_exprs(exprs)
+   for _, expr in ipairs(exprs) do
+      if type(expr) == "table" then
+         self:calc_expr(expr)
+      end
+   end
+end
+
+function CyclomaticComplexityMetric:calc_item_Eval(item)
+   self:calc_expr(item.node)
+end
+
+function CyclomaticComplexityMetric:calc_item_Local(item)
+   if item.rhs then
+      self:calc_exprs(item.rhs)
+   end
+end
+
+function CyclomaticComplexityMetric:calc_item_Set(item)
+   self:calc_exprs(item.rhs)
+end
+
+function CyclomaticComplexityMetric:calc_item(item)
+   local f = self["calc_item_" .. item.tag]
+   if f then
+      f(self, item)
+   end
+end
+
+function CyclomaticComplexityMetric:calc_items(items)
+   for _, item in ipairs(items) do
+      self:calc_item(item)
+   end
+end
+
+-- stmt if: {condition, block; condition, block; ... else_block}
+function CyclomaticComplexityMetric:calc_stmt_If(node)
+   for i = 1, #node - 1, 2 do
+      self:incr_decisions(1)
+      self:calc_stmts(node[i+1])
+   end
+
+   if #node % 2 == 1 then
+      self:calc_stmts(node[#node])
+   end
+end
+
+-- stmt while: {condition, block}
+function CyclomaticComplexityMetric:calc_stmt_While(node)
+   self:incr_decisions(1)
+   self:calc_stmts(node[2])
+end
+
+-- stmt repeat: {block, condition}
+function CyclomaticComplexityMetric:calc_stmt_Repeat(node)
+   self:incr_decisions(1)
+   self:calc_stmts(node[1])
+end
+
+-- stmt forin: {iter_vars, expression_list, block}
+function CyclomaticComplexityMetric:calc_stmt_Forin(node)
+   self:incr_decisions(1)
+   self:calc_stmts(node[3])
+end
+
+-- stmt fornum: {first_var, expression, expression, expression[optional], block}
+function CyclomaticComplexityMetric:calc_stmt_Fornum(node)
+   self:incr_decisions(1)
+   self:calc_stmts(node[5] or node[4])
+end
+
+function CyclomaticComplexityMetric:calc_stmt(node)
+   local f = self["calc_stmt_" .. node.tag]
+   if f then
+      f(self, node)
+   end
+end
+
+function CyclomaticComplexityMetric:calc_stmts(stmts)
+   for _, stmt in ipairs(stmts) do
+      self:calc_stmt(stmt)
+   end
+end
+
+-- Cyclomatic complexity of a function equals to the number of decision points plus 1.
+function CyclomaticComplexityMetric:report(chstate, line)
+   self.count = 1
+   self:calc_stmts(line.node[2])
+   self:calc_items(line.items)
+   warn_cyclomatic_complexity(chstate, line, self.count)
+end
+
+function stage.run(chstate)
+   local ccmetric = CyclomaticComplexityMetric()
+
+   for _, line in ipairs(chstate.lines) do
+      ccmetric:report(chstate, line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_empty_blocks.lua b/script/luacheck/stages/detect_empty_blocks.lua
new file mode 100755
index 000000000..b46d89621
--- /dev/null
+++ b/script/luacheck/stages/detect_empty_blocks.lua
@@ -0,0 +1,36 @@
+local core_utils = require "luacheck.core_utils"
+
+local stage = {}
+
+stage.warnings = {
+   ["541"] = {message_format = "empty do..end block", fields = {}},
+   ["542"] = {message_format = "empty if branch", fields = {}}
+}
+
+local function check_block(chstate, block, code)
+   if #block == 0 then
+      chstate:warn_range(code, block)
+   end
+end
+
+
+local function check_node(chstate, node)
+   if node.tag == "Do" then
+      check_block(chstate, node, "541")
+      return
+   end
+
+   for index = 2, #node, 2 do
+      check_block(chstate, node[index], "542")
+   end
+
+   if #node % 2 == 1 then
+      check_block(chstate, node[#node], "542")
+   end
+end
+
+function stage.run(chstate)
+   core_utils.each_statement(chstate, {"Do", "If"}, check_node)
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_empty_statements.lua b/script/luacheck/stages/detect_empty_statements.lua
new file mode 100755
index 000000000..0a9437811
--- /dev/null
+++ b/script/luacheck/stages/detect_empty_statements.lua
@@ -0,0 +1,13 @@
+local stage = {}
+
+stage.warnings = {
+   ["551"] = {message_format = "empty statement", fields = {}}
+}
+
+function stage.run(chstate)
+   for _, range in ipairs(chstate.useless_semicolons) do
+      chstate:warn_range("551", range)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_globals.lua b/script/luacheck/stages/detect_globals.lua
new file mode 100755
index 000000000..59ed41b0d
--- /dev/null
+++ b/script/luacheck/stages/detect_globals.lua
@@ -0,0 +1,252 @@
+local utils = require "luacheck.utils"
+
+local stage = {}
+
+local function prefix_if_indirect(message)
+   return function(warning)
+      if warning.indirect then
+         return "indirectly " .. message
+      else
+         return message
+      end
+   end
+end
+
+local function setting_global_format_message(warning)
+   -- `module` field is set during filtering.
+   if warning.module then
+      return "setting non-module global variable {name!}"
+   else
+      return "setting non-standard global variable {name!}"
+   end
+end
+local global_warning_fields = {"name", "indexing", "previous_indexing_len", "top", "indirect"}
+
+stage.warnings = {
+   ["111"] = {message_format = setting_global_format_message, fields = global_warning_fields},
+   ["112"] = {message_format = "mutating non-standard global variable {name!}", fields = global_warning_fields},
+   ["113"] = {message_format = "accessing undefined variable {name!}", fields = global_warning_fields},
+   -- The following warnings are added during filtering.
+   ["121"] = {message_format = "setting read-only global variable {name!}", fields = {}},
+   ["122"] = {message_format = prefix_if_indirect("setting read-only field {field!} of global {name!}"), fields = {}},
+   ["131"] = {message_format = "unused global variable {name!}", fields = {}},
+   ["142"] = {message_format = prefix_if_indirect("setting undefined field {field!} of global {name!}"), fields = {}},
+   ["143"] = {message_format = prefix_if_indirect("accessing undefined field {field!} of global {name!}"), fields = {}}
+}
+
+local action_codes = {
+   set = "1",
+   mutate = "2",
+   access = "3"
+}
+
+-- `index` describes an indexing, where `index[1]` is a global node
+-- and other items describe keys: each one is a string node, "not_string",
+-- or "unknown". `node` is literal base node that's indexed.
+-- E.g. in `local a = table.a; a.b = "c"` `node` is `a` node of the second
+-- statement and `index` describes `table.a.b`.
+-- `index.previous_indexing_len` is optional length of prefix of `index` array representing last assignment
+-- in the aliasing chain, e.g. `2` in the previous example (because last indexing is `table.a`).
+local function warn_global(chstate, node, index, is_lhs, is_top_line)
+   local global = index[1]
+   local action = is_lhs and (#index == 1 and "set" or "mutate") or "access"
+
+   local indexing
+
+   if #index > 1 then
+      indexing = {}
+
+      for i, field in ipairs(index) do
+         if i > 1 then
+            if field == "unknown" then
+               indexing[i - 1] = true
+            elseif field == "not_string" then
+               indexing[i - 1] = false
+            else
+               indexing[i - 1] = field[1]
+            end
+         end
+      end
+   end
+
+   chstate:warn_range("11" .. action_codes[action], node, {
+      name = global[1],
+      indexing = indexing,
+      previous_indexing_len = index.previous_indexing_len,
+      top = is_top_line and action == "set" or nil,
+      indirect = node ~= global or nil
+   })
+end
+
+local function resolved_to_index(resolution)
+   return resolution ~= "unknown" and resolution ~= "not_string" and resolution.tag ~= "String"
+end
+
+local literal_tags = utils.array_to_set({"Nil", "True", "False", "Number", "String", "Table", "Function"})
+
+local deep_resolve -- Forward declaration.
+
+local function resolve_node(node, item)
+   if node.tag == "Id" or node.tag == "Index" then
+      deep_resolve(node, item)
+      return node.resolution
+   elseif literal_tags[node.tag] then
+      return node.tag == "String" and node or "not_string"
+   else
+      return "unknown"
+   end
+end
+
+-- Resolves value of an identifier or index node, tracking through simple
+-- assignments like `local foo = bar.baz`.
+-- Can be given an `Invoke` node to resolve the method field.
+-- Sets `node.resolution` to "unknown", "not_string", `string node`, or
+-- {previous_indexing_len = index, global_node, key...}.
+-- Each key can be "unknown", "not_string" or `string_node`.
+function deep_resolve(node, item)
+   if node.resolution then
+      return
+   end
+
+   -- Common case.
+   -- Also protects against infinite recursion, if it's even possible.
+   node.resolution = "unknown"
+
+   local base = node
+   local base_tag = node.tag == "Id" and "Id" or "Index"
+   local keys = {}
+
+   while base_tag == "Index" do
+      table.insert(keys, 1, base[2])
+      base = base[1]
+      base_tag = base.tag
+   end
+
+   if base_tag ~= "Id" then
+      return
+   end
+
+   local var = base.var
+   local base_resolution
+   local previous_indexing_len
+
+   if var then
+      if not item.used_values[var] or #item.used_values[var] ~= 1 then
+         -- Do not know where the value for the base local came from.
+         return
+      end
+
+      local value = item.used_values[var][1]
+
+      if not value.node then
+         return
+      end
+
+      base_resolution = resolve_node(value.node, value.item)
+
+      if resolved_to_index(base_resolution) then
+         previous_indexing_len = #base_resolution
+      end
+   else
+      base_resolution = {base}
+   end
+
+   if #keys == 0 then
+      node.resolution = base_resolution
+   elseif not resolved_to_index(base_resolution) then
+      -- Indexing something unknown or indexing a literal.
+      node.resolution = "unknown"
+   else
+      local resolution = utils.update({}, base_resolution)
+      resolution.previous_indexing_len = previous_indexing_len
+
+      for _, key in ipairs(keys) do
+         local key_resolution = resolve_node(key, item)
+
+         if resolved_to_index(key_resolution) then
+            key_resolution = "unknown"
+         end
+
+         table.insert(resolution, key_resolution)
+      end
+
+      -- Assign resolution only after all the recursive calls.
+      node.resolution = resolution
+   end
+end
+
+local function detect_in_node(chstate, item, node, is_top_line, is_lhs)
+   if node.tag == "Index" or node.tag == "Invoke" or node.tag == "Id" then
+      if node.tag == "Id" and node.var then
+         -- Do not warn about assignments to and accesses of local variables
+         -- that resolve to globals or their fields.
+         return
+      end
+
+      deep_resolve(node, item)
+      local resolution = node.resolution
+
+      -- Still need to recurse into base and key nodes.
+      -- E.g. don't miss a global in `(global1())[global2()].
+
+      if node.tag == "Invoke" then
+         for i = 3, #node do
+            detect_in_node(chstate, item, node[i], is_top_line)
+         end
+      end
+
+      if node.tag ~= "Id" then
+         repeat
+            detect_in_node(chstate, item, node[2], is_top_line)
+            node = node[1]
+         until node.tag ~= "Index"
+
+         if node.tag ~= "Id" then
+            detect_in_node(chstate, item, node, is_top_line)
+         end
+      end
+
+      if resolved_to_index(resolution) then
+         warn_global(chstate, node, resolution, is_lhs, is_top_line)
+      end
+   elseif node.tag ~= "Function" then
+      for _, nested_node in ipairs(node) do
+         if type(nested_node) == "table" then
+            detect_in_node(chstate, item, nested_node, is_top_line)
+         end
+      end
+   end
+end
+
+local function detect_in_nodes(chstate, item, nodes, is_top_line, is_lhs)
+   for _, node in ipairs(nodes) do
+      detect_in_node(chstate, item, node, is_top_line, is_lhs)
+   end
+end
+
+local function detect_globals_in_line(chstate, line)
+   local is_top_line = line == chstate.top_line
+
+   for _, item in ipairs(line.items) do
+      if item.tag == "Eval" then
+         detect_in_node(chstate, item, item.node, is_top_line)
+      elseif item.tag == "Local" then
+         if item.rhs then
+            detect_in_nodes(chstate, item, item.rhs, is_top_line)
+         end
+      elseif item.tag == "Set" then
+         detect_in_nodes(chstate, item, item.lhs, is_top_line, true)
+         detect_in_nodes(chstate, item, item.rhs, is_top_line)
+      end
+   end
+end
+
+-- Warns about assignments, field accesses, and mutations of global variables,
+-- tracing through localizing assignments such as `local t = table`.
+function stage.run(chstate)
+   for _, line in ipairs(chstate.lines) do
+      detect_globals_in_line(chstate, line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_reversed_fornum_loops.lua b/script/luacheck/stages/detect_reversed_fornum_loops.lua
new file mode 100755
index 000000000..b09d81930
--- /dev/null
+++ b/script/luacheck/stages/detect_reversed_fornum_loops.lua
@@ -0,0 +1,39 @@
+local core_utils = require "luacheck.core_utils"
+
+local stage = {}
+
+stage.warnings = {
+   ["571"] = {message_format = "numeric for loop goes from #(expr) down to {limit} but loop step is not negative",
+      fields = {"limit"}}
+}
+
+local function check_fornum(chstate, node)
+   if node[2].tag ~= "Op" or node[2][1] ~= "len" then
+      return
+   end
+
+   local limit, limit_repr = core_utils.eval_const_node(node[3])
+
+   if not limit or limit > 1 then
+      return
+   end
+
+   local step = 1
+
+   if node[5] then
+      step = core_utils.eval_const_node(node[4])
+   end
+
+   if step and step >= 0 then
+      chstate:warn_range("571", node, {
+         limit = limit_repr
+      })
+   end
+end
+
+-- Warns about loops trying to go from `#(expr)` to `1` with positive step.
+function stage.run(chstate)
+   core_utils.each_statement(chstate, {"Fornum"}, check_fornum)
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_unbalanced_assignments.lua b/script/luacheck/stages/detect_unbalanced_assignments.lua
new file mode 100755
index 000000000..ab28bf406
--- /dev/null
+++ b/script/luacheck/stages/detect_unbalanced_assignments.lua
@@ -0,0 +1,34 @@
+local core_utils = require "luacheck.core_utils"
+
+local stage = {}
+
+stage.warnings = {
+   ["531"] = {message_format = "right side of assignment has more values than left side expects", fields = {}},
+   ["532"] = {message_format = "right side of assignment has less values than left side expects", fields = {}}
+}
+
+local function is_unpacking(node)
+   return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke"
+end
+
+local function check_assignment(chstate, node)
+   local rhs = node[2]
+
+   if not rhs then
+      return
+   end
+
+   local lhs = node[1]
+
+   if #rhs > #lhs then
+      chstate:warn_range("531", node)
+   elseif #rhs < #lhs and node.tag == "Set" and not is_unpacking(rhs[#rhs]) then
+      chstate:warn_range("532", node)
+   end
+end
+
+function stage.run(chstate)
+   core_utils.each_statement(chstate, {"Set", "Local"}, check_assignment)
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_uninit_accesses.lua b/script/luacheck/stages/detect_uninit_accesses.lua
new file mode 100755
index 000000000..3afe8d2a8
--- /dev/null
+++ b/script/luacheck/stages/detect_uninit_accesses.lua
@@ -0,0 +1,54 @@
+local stage = {}
+
+stage.warnings = {
+   ["321"] = {message_format = "accessing uninitialized variable {name!}", fields = {"name"}},
+   ["341"] = {message_format = "mutating uninitialized variable {name!}", fields = {"name"}}
+}
+
+local function detect_uninit_access_in_line(chstate, line)
+   for _, item in ipairs(line.items) do
+      for _, action_key in ipairs({"accesses", "mutations"}) do
+         local code = action_key == "accesses" and "321" or "341"
+         local item_var_map = item[action_key]
+
+         if item_var_map then
+            for var, accessing_nodes in pairs(item_var_map) do
+               -- If there are no values at all reaching this access, not even the empty one,
+               -- this item (or a closure containing it) is not reachable from variable definition.
+               -- It will be reported as unreachable code, no need to report uninitalized accesses in it.
+               if item.used_values[var] then
+                  -- If this variable is has only one, empty value then it's already reported as never set,
+                  -- no need to report each access.
+                  if not (#var.values == 1 and var.values[1].empty) then
+                     local all_possible_values_empty = true
+
+                     for _, possible_value in ipairs(item.used_values[var]) do
+                        if not possible_value.empty then
+                           all_possible_values_empty = false
+                           break
+                        end
+                     end
+
+                     if all_possible_values_empty then
+                        for _, accessing_node in ipairs(accessing_nodes) do
+                           chstate:warn_range(code, accessing_node, {
+                              name = accessing_node[1]
+                           })
+                        end
+                     end
+                  end
+               end
+            end
+         end
+      end
+   end
+end
+
+-- Warns about accesses and mutations that don't resolve to any values except initial empty one.
+function stage.run(chstate)
+   for _, line in ipairs(chstate.lines) do
+      detect_uninit_access_in_line(chstate, line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_unreachable_code.lua b/script/luacheck/stages/detect_unreachable_code.lua
new file mode 100755
index 000000000..f2003dcff
--- /dev/null
+++ b/script/luacheck/stages/detect_unreachable_code.lua
@@ -0,0 +1,36 @@
+local stage = {}
+
+stage.warnings = {
+   ["511"] = {message_format = "unreachable code", fields = {}},
+   ["512"] = {message_format = "loop is executed at most once", fields = {}}
+}
+
+local function noop_callback() end
+
+local function detect_unreachable_code(chstate, line)
+   local reachable_indexes = {}
+
+   -- Mark all items reachable from the function start.
+   line:walk(reachable_indexes, 1, noop_callback)
+
+   -- All remaining items are unreachable.
+   -- However, there is no point in reporting all of them.
+   -- Only report those that are not reachable from any already reported ones.
+   for item_index, item in ipairs(line.items) do
+      if not reachable_indexes[item_index] then
+         if item.node then
+            chstate:warn_range(item.loop_end and "512" or "511", item.node)
+            -- Mark all items reachable from the item just reported.
+            line:walk(reachable_indexes, item_index, noop_callback)
+         end
+      end
+   end
+end
+
+function stage.run(chstate)
+   for _, line in ipairs(chstate.lines) do
+      detect_unreachable_code(chstate, line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_unused_fields.lua b/script/luacheck/stages/detect_unused_fields.lua
new file mode 100755
index 000000000..2f4cdea6a
--- /dev/null
+++ b/script/luacheck/stages/detect_unused_fields.lua
@@ -0,0 +1,81 @@
+local core_utils = require "luacheck.core_utils"
+
+local stage = {}
+
+local function unused_field_value_message_format(warning)
+   local target = warning.index and "index" or "field"
+   return "value assigned to " .. target .. " {field!} is overwritten on line {overwritten_line} before use"
+end
+
+stage.warnings = {
+   ["314"] = {message_format = unused_field_value_message_format,
+      fields = {"field", "index", "overwritten_line","overwritten_column", "overwritten_end_column"}}
+}
+
+local function warn_unused_field_value(chstate, node, field_repr, is_index, overwriting_node)
+   chstate:warn_range("314", node, {
+      field = field_repr,
+      index = is_index,
+      overwritten_line = overwriting_node.line,
+      overwritten_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.offset),
+      overwritten_end_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.end_offset)
+   })
+end
+
+local function check_table(chstate, node)
+   local array_index = 1.0
+   local key_value_to_node = {}
+   local key_node_to_repr = {}
+   local index_key_nodes = {}
+
+   for _, pair in ipairs(node) do
+      local key_value
+      local key_repr
+      local key_node
+
+      if pair.tag == "Pair" then
+         key_node = pair[1]
+         key_value, key_repr = core_utils.eval_const_node(key_node)
+      else
+         key_node = pair
+         key_value = array_index
+         key_repr = tostring(math.floor(key_value))
+         array_index = array_index + 1.0
+      end
+
+      if key_value then
+         local prev_key_node = key_value_to_node[key_value]
+         local prev_key_repr = key_node_to_repr[prev_key_node]
+         local prev_key_is_index = index_key_nodes[prev_key_node]
+
+         if prev_key_node then
+            warn_unused_field_value(chstate, prev_key_node, prev_key_repr, prev_key_is_index, key_node)
+         end
+
+         key_value_to_node[key_value] = key_node
+         key_node_to_repr[key_node] = key_repr
+
+         if pair.tag ~= "Pair" then
+            index_key_nodes[key_node] = true
+         end
+      end
+   end
+end
+
+local function check_nodes(chstate, nodes)
+   for _, node in ipairs(nodes) do
+      if type(node) == "table" then
+         if node.tag == "Table" then
+            check_table(chstate, node)
+         end
+
+         check_nodes(chstate, node)
+      end
+   end
+end
+
+function stage.run(chstate)
+   check_nodes(chstate, chstate.ast)
+end
+
+return stage
diff --git a/script/luacheck/stages/detect_unused_locals.lua b/script/luacheck/stages/detect_unused_locals.lua
new file mode 100755
index 000000000..47a5b8b95
--- /dev/null
+++ b/script/luacheck/stages/detect_unused_locals.lua
@@ -0,0 +1,330 @@
+local utils = require "luacheck.utils"
+
+local stage = {}
+
+local function unused_local_message_format(warning)
+   if warning.func then
+      if warning.recursive then
+         return "unused recursive function {name!}"
+      elseif warning.mutually_recursive then
+         return "unused mutually recursive function {name!}"
+      else
+         return "unused function {name!}"
+      end
+   else
+      return "unused variable {name!}"
+   end
+end
+
+local function unused_arg_message_format(warning)
+   if warning.name == "..." then
+      return "unused variable length argument"
+   else
+      return "unused argument {name!}"
+   end
+end
+
+local function unused_or_overwritten_warning(message_format)
+   return {
+      message_format = function(warning)
+         if warning.overwritten_line then
+            return message_format .. " is overwritten on line {overwritten_line} before use"
+         else
+            return message_format .. " is unused"
+         end
+      end,
+      fields = {"name", "secondary", "overwritten_line", "overwritten_column", "overwritten_end_column"}
+   }
+end
+
+stage.warnings = {
+   ["211"] = {message_format = unused_local_message_format,
+      fields = {"name", "func", "secondary", "useless", "recursive", "mutually_recursive"}},
+   ["212"] = {message_format = unused_arg_message_format, fields = {"name", "self"}},
+   ["213"] = {message_format = "unused loop variable {name!}", fields = {"name"}},
+   ["221"] = {message_format = "variable {name!} is never set", fields = {"name", "secondary"}},
+   ["231"] = {message_format = "variable {name!} is never accessed", fields = {"name", "secondary"}},
+   ["232"] = {message_format = "argument {name!} is never accessed", fields = {"name"}},
+   ["233"] = {message_format = "loop variable {name!} is never accessed", fields = {"name"}},
+   ["241"] = {message_format = "variable {name!} is mutated but never accessed", fields = {"name", "secondary"}},
+   ["311"] = unused_or_overwritten_warning("value assigned to variable {name!}"),
+   ["312"] = unused_or_overwritten_warning("value of argument {name!}"),
+   ["313"] = unused_or_overwritten_warning("value of loop variable {name!}"),
+   ["331"] = {message_format = "value assigned to variable {name!} is mutated but never accessed",
+      fields = {"name", "secondary"}}
+}
+
+local function is_secondary(value)
+   return value.secondaries and value.secondaries.used
+end
+
+local type_codes = {
+   var = "1",
+   func = "1",
+   arg = "2",
+   loop = "3",
+   loopi = "3"
+}
+
+local function warn_unused_var(chstate, value, is_useless)
+   chstate:warn_value("21" .. type_codes[value.var.type], value, {
+      secondary = is_secondary(value) or nil,
+      func = value.type == "func" or nil,
+      self = value.var.self,
+      useless = value.var.name == "_" and is_useless or nil
+   })
+end
+
+local function warn_unaccessed_var(chstate, var, is_mutated)
+   -- Mark as secondary if all assigned values are secondary.
+   -- It is guaranteed that there are at least two values.
+   local secondary = true
+
+   for _, value in ipairs(var.values) do
+      if not value.empty and not is_secondary(value) then
+         secondary = nil
+         break
+      end
+   end
+
+   chstate:warn_var("2" .. (is_mutated and "4" or "3") .. type_codes[var.type], var, {
+      secondary = secondary
+   })
+end
+
+local function warn_unused_value(chstate, value, overwriting_node)
+   local warning = chstate:warn_value("3" .. (value.mutated and "3" or "1") .. type_codes[value.type], value, {
+      secondary = is_secondary(value) or nil
+   })
+
+   if overwriting_node then
+      warning.overwritten_line = overwriting_node.line
+      warning.overwritten_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.offset)
+      warning.overwritten_end_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.end_offset)
+   end
+end
+
+-- Returns `true` if a variable should be reported as a function instead of simply local,
+-- `false` otherwise.
+-- A variable is considered a function if it has a single assignment and the value is a function,
+-- or if there is a forward declaration with a function assignment later.
+local function is_function_var(var)
+   return (#var.values == 1 and var.values[1].type == "func") or (
+      #var.values == 2 and var.values[1].empty and var.values[2].type == "func")
+end
+
+local externally_accessible_tags = utils.array_to_set({"Id", "Index", "Call", "Invoke", "Op", "Paren", "Dots"})
+
+local function is_externally_accessible(value)
+   return value.type ~= "var" or (value.node and externally_accessible_tags[value.node.tag])
+end
+
+local function get_overwriting_lhs_node(item, value)
+   for _, node in ipairs(item.lhs) do
+      if node.var == value.var then
+         return node
+      end
+   end
+end
+
+local function get_second_overwriting_lhs_node(item, value)
+   local after_value_node
+
+   for _, node in ipairs(item.lhs) do
+      if node.var == value.var then
+         if after_value_node then
+            return node
+         elseif node == value.var_node then
+            after_value_node = true
+         end
+      end
+   end
+end
+
+local function detect_unused_local(chstate, var)
+   if is_function_var(var) then
+      local value = var.values[2] or var.values[1]
+
+      if not value.used then
+         warn_unused_var(chstate, value)
+      end
+   elseif #var.values == 1 then
+      local value = var.values[1]
+
+      if not value.used then
+         if value.mutated then
+            if not is_externally_accessible(value) then
+               warn_unaccessed_var(chstate, var, true)
+            end
+         else
+            warn_unused_var(chstate, value, value.empty)
+         end
+      elseif value.empty then
+         chstate:warn_var("221", var)
+      end
+   elseif not var.accessed and not var.mutated then
+      warn_unaccessed_var(chstate, var)
+   else
+      local no_values_externally_accessible = true
+
+      for _, value in ipairs(var.values) do
+         if is_externally_accessible(value) then
+            no_values_externally_accessible = false
+         end
+      end
+
+      if not var.accessed and no_values_externally_accessible then
+         warn_unaccessed_var(chstate, var, true)
+      end
+
+      for _, value in ipairs(var.values) do
+         if not value.empty then
+            if not value.used then
+               if not value.mutated then
+                  local overwriting_node
+
+                  if value.overwriting_item then
+                     if value.overwriting_item ~= value.item then
+                        overwriting_node = get_overwriting_lhs_node(value.overwriting_item, value)
+                     end
+                  else
+                     overwriting_node = get_second_overwriting_lhs_node(value.item, value)
+                  end
+
+                  warn_unused_value(chstate, value, overwriting_node)
+               elseif not is_externally_accessible(value) then
+                  if var.accessed or not no_values_externally_accessible then
+                     warn_unused_value(chstate, value)
+                  end
+               end
+            end
+         end
+      end
+   end
+end
+
+local function detect_unused_locals_in_line(chstate, line)
+   for _, item in ipairs(line.items) do
+      if item.tag == "Local" then
+         for var in pairs(item.set_variables) do
+            -- Do not check the implicit top level vararg.
+            if var.node.line then
+               detect_unused_local(chstate, var)
+            end
+         end
+      end
+   end
+end
+
+local function detect_unused_locals(chstate)
+   for _, line in ipairs(chstate.lines) do
+      detect_unused_locals_in_line(chstate, line)
+   end
+end
+
+local function mark_reachable_lines(edges, marked, line)
+   for connected_line in pairs(edges[line]) do
+      if not marked[connected_line] then
+         marked[connected_line] = true
+         mark_reachable_lines(edges, marked, connected_line)
+      end
+   end
+end
+
+local function detect_unused_rec_funcs(chstate)
+   -- Build a graph of usage relations of all closures.
+   -- Closure A is used by closure B iff either B is parent
+   -- of A and A is not assigned to a local/upvalue, or
+   -- B uses local/upvalue value that is A.
+   -- Closures not reachable from root closure are unused,
+   -- report corresponding values/variables if not done already.
+
+   local line = chstate.top_line
+
+   -- Initialize edges maps.
+   local forward_edges = {[line] = {}}
+   local backward_edges = {[line] = {}}
+
+   for _, nested_line in ipairs(line.lines) do
+      forward_edges[nested_line] = {}
+      backward_edges[nested_line] = {}
+   end
+
+   -- Add edges leading to each nested line.
+   for _, nested_line in ipairs(line.lines) do
+      if nested_line.node.value then
+         for using_line in pairs(nested_line.node.value.using_lines) do
+            forward_edges[using_line][nested_line] = true
+            backward_edges[nested_line][using_line] = true
+         end
+      elseif nested_line.parent then
+         forward_edges[nested_line.parent][nested_line] = true
+         backward_edges[nested_line][nested_line.parent] = true
+      end
+   end
+
+   -- Recursively mark all closures reachable from root closure and unused closures.
+   -- Closures reachable from main chunk are used; closure reachable from unused closures
+   -- depend on that closure; that is, fixing warning about parent unused closure
+   -- fixes warning about the child one, so issuing a warning for the child is superfluous.
+   local marked = {[line] = true}
+   mark_reachable_lines(forward_edges, marked, line)
+
+   for _, nested_line in ipairs(line.lines) do
+      if nested_line.node.value and not nested_line.node.value.used then
+         marked[nested_line] = true
+         mark_reachable_lines(forward_edges, marked, nested_line)
+      end
+   end
+
+   -- Deal with unused closures.
+   for _, nested_line in ipairs(line.lines) do
+      local value = nested_line.node.value
+
+      if value and value.used and not marked[nested_line] then
+         -- This closure is used by some closure, but is not marked as reachable
+         -- from main chunk or any of reported closures.
+         -- Find candidate group of mutually recursive functions containing this one:
+         -- mark sets of closures reachable from it by forward and backward edges,
+         -- intersect them. Ignore already marked closures in the process to avoid
+         -- issuing superfluous, dependent warnings.
+         local forward_marked = setmetatable({}, {__index = marked})
+         local backward_marked = setmetatable({}, {__index = marked})
+         mark_reachable_lines(forward_edges, forward_marked, nested_line)
+         mark_reachable_lines(backward_edges, backward_marked, nested_line)
+
+         -- Iterate over closures in the group.
+         for mut_rec_line in pairs(forward_marked) do
+            if rawget(backward_marked, mut_rec_line) then
+               marked[mut_rec_line] = true
+               value = mut_rec_line.node.value
+
+               if value then
+                  -- Report this closure as self recursive or mutually recursive.
+                  local is_self_recursive = forward_edges[mut_rec_line][mut_rec_line]
+
+                  if is_function_var(value.var) then
+                     chstate:warn_value("211", value, {
+                        func = true,
+                        mutually_recursive = not is_self_recursive or nil,
+                        recursive = is_self_recursive or nil
+                     })
+                  else
+                     chstate:warn_value("311", value)
+                  end
+               end
+            end
+         end
+      end
+   end
+end
+
+-- Warns about unused local variables and their values as well as locals that
+-- are accessed but never set or set but never accessed.
+-- Warns about unused recursive functions.
+function stage.run(chstate)
+   detect_unused_locals(chstate)
+   detect_unused_rec_funcs(chstate)
+end
+
+return stage
diff --git a/script/luacheck/stages/init.lua b/script/luacheck/stages/init.lua
new file mode 100755
index 000000000..03ff9c796
--- /dev/null
+++ b/script/luacheck/stages/init.lua
@@ -0,0 +1,77 @@
+local utils = require "luacheck.utils"
+
+local stages = {}
+
+-- Checking is organized into stages run one after another.
+-- Each stage is in its own module and provides `run` function operating on a check state,
+-- and optionally `warnings` table mapping issue codes to tables with fields `message_format`
+-- containing format string for the issue or a function returning it given the issue,
+-- and `fields` containing array of extra fields this warning can have.
+
+stages.names = {
+   "parse",
+   "unwrap_parens",
+   "linearize",
+   "parse_inline_options",
+   "name_functions",
+   "resolve_locals",
+   "detect_bad_whitespace",
+   "detect_cyclomatic_complexity",
+   "detect_empty_blocks",
+   "detect_empty_statements",
+   "detect_globals",
+   "detect_reversed_fornum_loops",
+   "detect_unbalanced_assignments",
+   "detect_uninit_accesses",
+   "detect_unreachable_code",
+   "detect_unused_fields",
+   "detect_unused_locals"
+}
+
+local log = require("log")
+stages.modules = {}
+
+for _, name in ipairs(stages.names) do
+   local m,_ = require("luacheck.stages." .. name)
+   table.insert(stages.modules, m)
+end
+
+stages.warnings = {}
+
+local base_fields = {"code", "line", "column", "end_column"}
+
+local function register_warnings(warnings)
+   for code, warning in pairs(warnings) do
+      assert(not stages.warnings[code])
+      assert(warning.message_format)
+      assert(warning.fields)
+
+      local full_fields = utils.concat_arrays({base_fields, warning.fields})
+
+      stages.warnings[code] = {
+         message_format = warning.message_format,
+         fields = full_fields,
+         fields_set = utils.array_to_set(full_fields)
+      }
+   end
+end
+
+-- Issues that do not originate from normal check stages (excluding global related ones).
+register_warnings({
+   ["011"] = {message_format = "{msg}", fields = {"msg", "prev_line", "prev_column", "prev_end_column"}},
+   ["631"] = {message_format = "line is too long ({end_column} > {max_length})", fields = {}}
+})
+
+for _, stage_module in ipairs(stages.modules) do
+   if stage_module.warnings then
+      register_warnings(stage_module.warnings)
+   end
+end
+
+function stages.run(chstate)
+   for _, stage_module in ipairs(stages.modules) do
+      stage_module.run(chstate)
+   end
+end
+
+return stages
diff --git a/script/luacheck/stages/linearize.lua b/script/luacheck/stages/linearize.lua
new file mode 100755
index 000000000..d157227ea
--- /dev/null
+++ b/script/luacheck/stages/linearize.lua
@@ -0,0 +1,708 @@
+local parser = require "luacheck.parser"
+local utils = require "luacheck.utils"
+
+local stage = {}
+
+local function redefined_warning(message_format)
+   return {
+      message_format = message_format,
+      fields = {"name", "prev_line", "prev_column", "prev_end_column", "self"}
+   }
+end
+
+stage.warnings = {
+   ["411"] = redefined_warning("variable {name!} was previously defined on line {prev_line}"),
+   ["412"] = redefined_warning("variable {name!} was previously defined as an argument on line {prev_line}"),
+   ["413"] = redefined_warning("variable {name!} was previously defined as a loop variable on line {prev_line}"),
+   ["421"] = redefined_warning("shadowing definition of variable {name!} on line {prev_line}"),
+   ["422"] = redefined_warning("shadowing definition of argument {name!} on line {prev_line}"),
+   ["423"] = redefined_warning("shadowing definition of loop variable {name!} on line {prev_line}"),
+   ["431"] = redefined_warning("shadowing upvalue {name!} on line {prev_line}"),
+   ["432"] = redefined_warning("shadowing upvalue argument {name!} on line {prev_line}"),
+   ["433"] = redefined_warning("shadowing upvalue loop variable {name!} on line {prev_line}"),
+   ["521"] = {message_format = "unused label {label!}", fields = {"label"}}
+}
+
+local type_codes = {
+   var = "1",
+   func = "1",
+   arg = "2",
+   loop = "3",
+   loopi = "3"
+}
+
+local function warn_redefined(chstate, var, prev_var, is_same_scope)
+   local code = "4" .. (is_same_scope and "1" or var.line == prev_var.line and "2" or "3") .. type_codes[prev_var.type]
+
+   chstate:warn_var(code, var, {
+      self = var.self and prev_var.self,
+      prev_line = prev_var.node.line,
+      prev_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.offset),
+      prev_end_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.end_offset)
+   })
+end
+
+local function warn_unused_label(chstate, label)
+   chstate:warn_range("521", label.range, {
+      label = label.name
+   })
+end
+
+local pseudo_labels = utils.array_to_set({"do", "else", "break", "end", "return"})
+
+local Line = utils.class()
+
+function Line:__init(node, parent, value)
+   -- Maps variables to arrays of accessing items.
+   self.accessed_upvalues = {}
+   -- Maps variables to arrays of mutating items.
+   self.mutated_upvalues = {}
+   -- Maps variables to arays of setting items.
+   self.set_upvalues = {}
+   self.lines = {}
+   self.node = node
+   self.parent = parent
+   self.value = value
+   self.items = utils.Stack()
+end
+
+-- Calls callback with line, index, item, ... for each item reachable from starting item.
+-- `visited` is a set of already visited indexes.
+-- Callback can return true to stop walking from current item.
+function Line:walk(visited, index, callback, ...)
+   if visited[index] then
+      return
+   end
+
+   visited[index] = true
+
+   local item = self.items[index]
+
+   if callback(self, index, item, ...) then
+      return
+   end
+
+   if not item then
+      return
+   elseif item.tag == "Jump" then
+      return self:walk(visited, item.to, callback, ...)
+   elseif item.tag == "Cjump" then
+      self:walk(visited, item.to, callback, ...)
+   end
+
+   return self:walk(visited, index + 1, callback, ...)
+end
+
+local function new_scope(line)
+   return {
+      vars = {},
+      labels = {},
+      gotos = {},
+      line = line
+   }
+end
+
+local function new_var(line, node, type_)
+   return {
+      name = node[1],
+      node = node,
+      type = type_,
+      self = node.implicit,
+      line = line,
+      scope_start = line.items.size + 1,
+      values = {}
+   }
+end
+
+local function new_value(var_node, value_node, item, is_init)
+   local value = {
+      var = var_node.var,
+      var_node = var_node,
+      type = is_init and var_node.var.type or "var",
+      node = value_node,
+      using_lines = {},
+      empty = is_init and not value_node and (var_node.var.type == "var"),
+      item = item
+   }
+
+   if value_node and value_node.tag == "Function" then
+      value.type = "func"
+      value_node.value = value
+   end
+
+   return value
+end
+
+local function new_label(line, name, range)
+   return {
+      name = name,
+      range = range,
+      index = line.items.size + 1
+   }
+end
+
+local function new_goto(name, jump, range)
+   return {
+      name = name,
+      jump = jump,
+      range = range
+   }
+end
+
+local function new_jump_item(is_conditional)
+   return {
+      tag = is_conditional and "Cjump" or "Jump"
+   }
+end
+
+local function new_eval_item(node)
+   return {
+      tag = "Eval",
+      node = node,
+      accesses = {},
+      used_values = {},
+      lines = {}
+   }
+end
+
+local function new_noop_item(node, loop_end)
+   return {
+      tag = "Noop",
+      node = node,
+      loop_end = loop_end
+   }
+end
+
+local function new_local_item(node)
+   return {
+      tag = "Local",
+      node = node,
+      lhs = node[1],
+      rhs = node[2],
+      accesses = node[2] and {},
+      used_values = node[2] and {},
+      lines = node[2] and {}
+   }
+end
+
+local function new_set_item(node)
+   return {
+      tag = "Set",
+      node = node,
+      lhs = node[1],
+      rhs = node[2],
+      accesses = {},
+      mutations = {},
+      used_values = {},
+      lines = {}
+   }
+end
+
+local function is_unpacking(node)
+   return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke"
+end
+
+local LinState = utils.class()
+
+function LinState:__init(chstate)
+   self.chstate = chstate
+   self.lines = utils.Stack()
+   self.scopes = utils.Stack()
+end
+
+function LinState:enter_scope()
+   self.scopes:push(new_scope(self.lines.top))
+end
+
+function LinState:leave_scope()
+   local left_scope = self.scopes:pop()
+   local prev_scope = self.scopes.top
+
+   for _, goto_ in ipairs(left_scope.gotos) do
+      local label = left_scope.labels[goto_.name]
+
+      if label then
+         goto_.jump.to = label.index
+         label.used = true
+      else
+         if not prev_scope or prev_scope.line ~= self.lines.top then
+            if goto_.name == "break" then
+               parser.syntax_error("'break' is not inside a loop", goto_.range)
+            else
+               parser.syntax_error(("no visible label '%s'"):format(goto_.name), goto_.range)
+            end
+         end
+
+         table.insert(prev_scope.gotos, goto_)
+      end
+   end
+
+   for name, label in pairs(left_scope.labels) do
+      if not label.used and not pseudo_labels[name] then
+         warn_unused_label(self.chstate, label)
+      end
+   end
+
+   for _, var in pairs(left_scope.vars) do
+      var.scope_end = self.lines.top.items.size
+   end
+end
+
+function LinState:register_var(node, type_)
+   local var = new_var(self.lines.top, node, type_)
+   local prev_var = self:resolve_var(var.name)
+
+   if prev_var then
+      local is_same_scope = self.scopes.top.vars[var.name]
+
+      if var.name ~= "..." then
+         warn_redefined(self.chstate, var, prev_var, is_same_scope)
+      end
+
+      if is_same_scope then
+         prev_var.scope_end = self.lines.top.items.size
+      end
+   end
+
+   self.scopes.top.vars[var.name] = var
+   node.var = var
+   return var
+end
+
+function LinState:register_vars(nodes, type_)
+   for _, node in ipairs(nodes) do
+      self:register_var(node, type_)
+   end
+end
+
+function LinState:resolve_var(name)
+   for _, scope in utils.ripairs(self.scopes) do
+      local var = scope.vars[name]
+
+      if var then
+         return var
+      end
+   end
+end
+
+function LinState:check_var(node)
+   if not node.var then
+      node.var = self:resolve_var(node[1])
+   end
+
+   return node.var
+end
+
+function LinState:register_label(name, range)
+   local prev_label = self.scopes.top.labels[name]
+
+   if prev_label then
+      assert(not pseudo_labels[name])
+      parser.syntax_error(("label '%s' already defined on line %d"):format(
+         name, prev_label.range.line), range, prev_label.range)
+   end
+
+   self.scopes.top.labels[name] = new_label(self.lines.top, name, range)
+end
+
+function LinState:emit(item)
+   self.lines.top.items:push(item)
+end
+
+function LinState:emit_goto(name, is_conditional, range)
+   local jump = new_jump_item(is_conditional)
+   self:emit(jump)
+   table.insert(self.scopes.top.gotos, new_goto(name, jump, range))
+end
+
+local tag_to_boolean = {
+   Nil = false, False = false,
+   True = true, Number = true, String = true, Table = true, Function = true
+}
+
+-- Emits goto that jumps to ::name:: if bool(cond_node) == false.
+function LinState:emit_cond_goto(name, cond_node)
+   local cond_bool = tag_to_boolean[cond_node.tag]
+
+   if cond_bool ~= true then
+      self:emit_goto(name, cond_bool ~= false)
+   end
+end
+
+function LinState:emit_noop(node, loop_end)
+   self:emit(new_noop_item(node, loop_end))
+end
+
+function LinState:emit_stmt(stmt)
+   self["emit_stmt_" .. stmt.tag](self, stmt)
+end
+
+function LinState:emit_stmts(stmts)
+   for _, stmt in ipairs(stmts) do
+      self:emit_stmt(stmt)
+   end
+end
+
+function LinState:emit_block(block)
+   self:enter_scope()
+   self:emit_stmts(block)
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_Do(node)
+   self:emit_noop(node)
+   self:emit_block(node)
+end
+
+function LinState:emit_stmt_While(node)
+   self:emit_noop(node)
+   self:enter_scope()
+   self:register_label("do")
+   self:emit_expr(node[1])
+   self:emit_cond_goto("break", node[1])
+   self:emit_block(node[2])
+   self:emit_noop(node, true)
+   self:emit_goto("do")
+   self:register_label("break")
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_Repeat(node)
+   self:emit_noop(node)
+   self:enter_scope()
+   self:register_label("do")
+   self:enter_scope()
+   self:emit_stmts(node[1])
+   self:emit_expr(node[2])
+   self:leave_scope()
+   self:emit_cond_goto("do", node[2])
+   self:register_label("break")
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_Fornum(node)
+   self:emit_noop(node)
+   self:emit_expr(node[2])
+   self:emit_expr(node[3])
+
+   if node[5] then
+      self:emit_expr(node[4])
+   end
+
+   self:enter_scope()
+   self:register_label("do")
+   self:emit_goto("break", true)
+   self:enter_scope()
+   self:emit(new_local_item({{node[1]}}))
+   self:register_var(node[1], "loopi")
+   self:emit_stmts(node[5] or node[4])
+   self:leave_scope()
+   self:emit_noop(node, true)
+   self:emit_goto("do")
+   self:register_label("break")
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_Forin(node)
+   self:emit_noop(node)
+   self:emit_exprs(node[2])
+   self:enter_scope()
+   self:register_label("do")
+   self:emit_goto("break", true)
+   self:enter_scope()
+   self:emit(new_local_item({node[1]}))
+   self:register_vars(node[1], "loop")
+   self:emit_stmts(node[3])
+   self:leave_scope()
+   self:emit_noop(node, true)
+   self:emit_goto("do")
+   self:register_label("break")
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_If(node)
+   self:emit_noop(node)
+   self:enter_scope()
+
+   for i = 1, #node - 1, 2 do
+      self:enter_scope()
+      self:emit_expr(node[i])
+      self:emit_cond_goto("else", node[i])
+      self:emit_block(node[i + 1])
+      self:emit_goto("end")
+      self:register_label("else")
+      self:leave_scope()
+   end
+
+   if #node % 2 == 1 then
+      self:emit_block(node[#node])
+   end
+
+   self:register_label("end")
+   self:leave_scope()
+end
+
+function LinState:emit_stmt_Label(node)
+   self:register_label(node[1], node)
+end
+
+function LinState:emit_stmt_Goto(node)
+   self:emit_noop(node)
+   self:emit_goto(node[1], false, node)
+end
+
+function LinState:emit_stmt_Break(node)
+   self:emit_goto("break", false, node)
+end
+
+function LinState:emit_stmt_Return(node)
+   self:emit_noop(node)
+   self:emit_exprs(node)
+   self:emit_goto("return")
+end
+
+function LinState:emit_expr(node)
+   local item = new_eval_item(node)
+   self:scan_expr(item, node)
+   self:emit(item)
+end
+
+function LinState:emit_exprs(exprs)
+   for _, expr in ipairs(exprs) do
+      self:emit_expr(expr)
+   end
+end
+
+LinState.emit_stmt_Call = LinState.emit_expr
+LinState.emit_stmt_Invoke = LinState.emit_expr
+
+function LinState:emit_stmt_Local(node)
+   local item = new_local_item(node)
+   self:emit(item)
+
+   if node[2] then
+      self:scan_exprs(item, node[2])
+   end
+
+   self:register_vars(node[1], "var")
+end
+
+function LinState:emit_stmt_Localrec(node)
+   local item = new_local_item(node)
+   self:register_var(node[1][1], "var")
+   self:emit(item)
+   self:scan_expr(item, node[2][1])
+end
+
+function LinState:emit_stmt_Set(node)
+   local item = new_set_item(node)
+   self:scan_exprs(item, node[2])
+
+   for _, expr in ipairs(node[1]) do
+      if expr.tag == "Id" then
+         local var = self:check_var(expr)
+
+         if var then
+            self:register_upvalue_action(item, var, "set_upvalues")
+         end
+      else
+         assert(expr.tag == "Index")
+         self:scan_lhs_index(item, expr)
+      end
+   end
+
+   self:emit(item)
+end
+
+
+function LinState:scan_expr(item, node)
+   local scanner = self["scan_expr_" .. node.tag]
+
+   if scanner then
+      scanner(self, item, node)
+   end
+end
+
+function LinState:scan_exprs(item, nodes)
+   for _, node in ipairs(nodes) do
+      self:scan_expr(item, node)
+   end
+end
+
+function LinState:register_upvalue_action(item, var, key)
+   for _, line in utils.ripairs(self.lines) do
+      if line == var.line then
+         break
+      end
+
+      if not line[key][var] then
+         line[key][var] = {}
+      end
+
+      table.insert(line[key][var], item)
+   end
+end
+
+function LinState:mark_access(item, node)
+   node.var.accessed = true
+
+   if not item.accesses[node.var] then
+      item.accesses[node.var] = {}
+   end
+
+   table.insert(item.accesses[node.var], node)
+   self:register_upvalue_action(item, node.var, "accessed_upvalues")
+end
+
+function LinState:mark_mutation(item, node)
+   node.var.mutated = true
+
+   if not item.mutations[node.var] then
+      item.mutations[node.var] = {}
+   end
+
+   table.insert(item.mutations[node.var], node)
+   self:register_upvalue_action(item, node.var, "mutated_upvalues")
+end
+
+function LinState:scan_expr_Id(item, node)
+   if self:check_var(node) then
+      self:mark_access(item, node)
+   end
+end
+
+function LinState:scan_expr_Dots(item, node)
+   local dots = self:check_var(node)
+
+   if not dots or dots.line ~= self.lines.top then
+      parser.syntax_error("cannot use '...' outside a vararg function", node)
+   end
+
+   self:mark_access(item, node)
+end
+
+function LinState:scan_lhs_index(item, node)
+   if node[1].tag == "Id" then
+      if self:check_var(node[1]) then
+         self:mark_mutation(item, node[1])
+      end
+   elseif node[1].tag == "Index" then
+      self:scan_lhs_index(item, node[1])
+   else
+      self:scan_expr(item, node[1])
+   end
+
+   self:scan_expr(item, node[2])
+end
+
+LinState.scan_expr_Index = LinState.scan_exprs
+LinState.scan_expr_Call = LinState.scan_exprs
+LinState.scan_expr_Invoke = LinState.scan_exprs
+LinState.scan_expr_Paren = LinState.scan_exprs
+LinState.scan_expr_Table = LinState.scan_exprs
+LinState.scan_expr_Pair = LinState.scan_exprs
+
+function LinState:scan_expr_Op(item, node)
+   self:scan_expr(item, node[2])
+
+   if node[3] then
+      self:scan_expr(item, node[3])
+   end
+end
+
+-- Puts tables {var = value{} into field `set_variables` of items in line which set values.
+-- Registers set values in field `values` of variables.
+function LinState:register_set_variables()
+   local line = self.lines.top
+
+   for _, item in ipairs(line.items) do
+      if item.tag == "Local" or item.tag == "Set" then
+         item.set_variables = {}
+
+         local is_init = item.tag == "Local"
+         local unpacking_item -- Rightmost item of rhs which may unpack into several lhs items.
+
+         if item.rhs then
+            local last_rhs_item = item.rhs[#item.rhs]
+
+            if is_unpacking(last_rhs_item) then
+               unpacking_item = last_rhs_item
+            end
+         end
+
+         local secondaries -- Array of values unpacked from rightmost rhs item.
+
+         if unpacking_item and (#item.lhs > #item.rhs) then
+            secondaries = {}
+         end
+
+         for i, node in ipairs(item.lhs) do
+            local value
+
+            if node.var then
+               value = new_value(node, item.rhs and item.rhs[i] or unpacking_item, item, is_init)
+               item.set_variables[node.var] = value
+               table.insert(node.var.values, value)
+            end
+
+            if secondaries and (i >= #item.rhs) then
+               if value then
+                  value.secondaries = secondaries
+                  table.insert(secondaries, value)
+               else
+                  -- If one of secondary values is assigned to a global or index,
+                  -- it is considered used.
+                  secondaries.used = true
+               end
+            end
+         end
+      end
+   end
+end
+
+function LinState:build_line(node)
+   self.lines:push(Line(node, self.lines.top))
+   self:enter_scope()
+   self:emit(new_local_item({node[1]}))
+   self:enter_scope()
+   self:register_vars(node[1], "arg")
+   self:emit_stmts(node[2])
+   self:leave_scope()
+   self:register_label("return")
+   self:leave_scope()
+   self:register_set_variables()
+   local line = self.lines:pop()
+
+   for _, prev_line in ipairs(self.lines) do
+      table.insert(prev_line.lines, line)
+   end
+
+   return line
+end
+
+function LinState:scan_expr_Function(item, node)
+   local line = self:build_line(node)
+   table.insert(item.lines, line)
+
+   for _, nested_line in ipairs(line.lines) do
+      table.insert(item.lines, nested_line)
+   end
+end
+
+-- Builds linear representation (line) of AST and assigns it as `chstate.top_line`.
+-- Assings an array of all lines as `chstate.lines`.
+-- Adds warnings for redefined/shadowed locals and unused labels.
+function stage.run(chstate)
+   local linstate = LinState(chstate)
+   chstate.top_line = linstate:build_line({{{tag = "Dots", "..."}}, chstate.ast})
+   assert(linstate.lines.size == 0)
+   assert(linstate.scopes.size == 0)
+
+   chstate.lines = {chstate.top_line}
+
+   for _, nested_line in ipairs(chstate.top_line.lines) do
+      table.insert(chstate.lines, nested_line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/name_functions.lua b/script/luacheck/stages/name_functions.lua
new file mode 100755
index 000000000..7ae363ebb
--- /dev/null
+++ b/script/luacheck/stages/name_functions.lua
@@ -0,0 +1,71 @@
+local stage = {}
+
+local function get_index_name(base_name, key_node)
+   if key_node.tag == "String" then
+      return base_name .. "." .. key_node[1]
+   end
+end
+
+local function get_full_field_name(node)
+   if node.tag == "Id" then
+      return node[1]
+   elseif node.tag == "Index" then
+      local base_name = get_full_field_name(node[1])
+      return base_name and get_index_name(base_name, node[2])
+   end
+end
+
+local handle_node
+
+local function handle_nodes(nodes)
+   for _, node in ipairs(nodes) do
+      if type(node) == "table" then
+         handle_node(node)
+      end
+   end
+end
+
+function handle_node(node, name)
+   if node.tag == "Function" then
+      node.name = name
+      handle_nodes(node[2])
+   elseif node.tag == "Set" or node.tag == "Local" or node.tag == "Localrec" then
+      local lhs = node[1]
+      local rhs = node[2]
+
+      -- No need to handle LHS if there is no RHS, it's always just a list of locals in that case.
+      if rhs then
+         handle_nodes(lhs)
+
+         for index, rhs_node in ipairs(rhs) do
+            local lhs_node = lhs[index]
+            local field_name = lhs_node and get_full_field_name(lhs_node)
+            handle_node(rhs_node, field_name)
+         end
+      end
+   elseif node.tag == "Table" and name then
+      for _, pair_node in ipairs(node) do
+         if pair_node.tag == "Pair" then
+            local key_node = pair_node[1]
+            local value_node = pair_node[2]
+            handle_node(key_node)
+            handle_node(value_node, get_index_name(name, key_node))
+         else
+            handle_node(pair_node)
+         end
+      end
+   else
+      handle_nodes(node)
+   end
+end
+
+-- Adds `name` field to `Function` ast nodes when possible:
+-- * Function assigned to a variable (doesn't matter if local or global): "foo".
+-- * Function assigned to a field: "foo.bar.baz".
+--   Function can be in a table assigned to a variable or a field, e.g. `foo.bar = {baz = function() ... end}`.
+-- * Otherwise: `nil`.
+function stage.run(chstate)
+   handle_nodes(chstate.ast)
+end
+
+return stage
diff --git a/script/luacheck/stages/parse.lua b/script/luacheck/stages/parse.lua
new file mode 100755
index 000000000..07e7e650d
--- /dev/null
+++ b/script/luacheck/stages/parse.lua
@@ -0,0 +1,19 @@
+local decoder = require "luacheck.decoder"
+local parser = require "luacheck.parser"
+
+local stage = {}
+
+function stage.run(chstate)
+   chstate.source = decoder.decode(chstate.source_bytes)
+   chstate.line_offsets = {}
+   chstate.line_lengths = {}
+   local ast, comments, code_lines, line_endings, useless_semicolons = parser.parse(
+      chstate.source, chstate.line_offsets, chstate.line_lengths)
+   chstate.ast = ast
+   chstate.comments = comments
+   chstate.code_lines = code_lines
+   chstate.line_endings = line_endings
+   chstate.useless_semicolons = useless_semicolons
+end
+
+return stage
diff --git a/script/luacheck/stages/parse_inline_options.lua b/script/luacheck/stages/parse_inline_options.lua
new file mode 100755
index 000000000..b8f7e5dba
--- /dev/null
+++ b/script/luacheck/stages/parse_inline_options.lua
@@ -0,0 +1,351 @@
+local options = require "luacheck.options"
+local utils = require "luacheck.utils"
+
+local stage = {}
+
+stage.warnings = {
+   -- Also produced during filtering for options that did not pass validation.
+   ["021"] = {message_format = "{msg}", fields = {"msg"}},
+   ["022"] = {message_format = "unpaired push directive", fields = {}},
+   ["023"] = {message_format = "unpaired pop directive", fields = {}}
+}
+
+stage.inline_option_fields = {"line", "pop_count", "options", "column", "end_column"}
+
+local limit_opts = utils.array_to_set({"max_line_length", "max_code_line_length", "max_string_line_length",
+   "max_comment_line_length", "max_cyclomatic_complexity"})
+
+local function is_valid_option_name(name)
+   if name == "std" or options.variadic_inline_options[name] then
+      return true
+   end
+
+   name = name:gsub("^no_", "")
+   return options.nullary_inline_options[name] or limit_opts[name]
+end
+
+-- Splits a token array for an inline option invocation into
+-- option name and argument array, or nil if invocation is invalid.
+local function split_invocation(tokens)
+   -- Name of the option can be split into several space separated tokens.
+   -- Since some valid names are prefixes of some other names
+   -- (e.g. `unused` and `unused arguments`), the longest prefix of token
+   -- array that is a valid option name should be considered.
+   local cur_name
+   local last_valid_name
+   local last_valid_name_end_index
+
+   for i, token in ipairs(tokens) do
+      cur_name = cur_name and (cur_name .. "_" .. token) or token
+
+      if is_valid_option_name(cur_name) then
+         last_valid_name = cur_name
+         last_valid_name_end_index = i
+      end
+   end
+
+   if not last_valid_name then
+      return
+   end
+
+   local args = {}
+
+   for i = last_valid_name_end_index + 1, #tokens do
+      table.insert(args, tokens[i])
+   end
+
+   return last_valid_name, args
+end
+
+local function unexpected_num_args(name, args, expected)
+   return ("inline option '%s' expects %d argument%s, %d given"):format(
+      name, expected, expected == 1 and "" or "s", #args)
+end
+
+-- Parses inline option body, returns options or nil and error message.
+local function parse_options(body)
+   local opts = {}
+
+   local parts = utils.split(body, ",")
+
+   for _, name_and_args in ipairs(parts) do
+      local tokens = utils.split(name_and_args)
+      local name, args = split_invocation(tokens)
+
+      if not name then
+         if #tokens == 0 then
+            return nil, (#parts == 1) and "empty inline option" or "empty inline option invocation"
+         else
+            return nil, ("unknown inline option '%s'"):format(table.concat(tokens, " "))
+         end
+      end
+
+      if name == "std" then
+         if #args ~= 1 then
+            return nil, unexpected_num_args(name, args, 1)
+         end
+
+         opts.std = args[1]
+      elseif name == "ignore" and #args == 0 then
+         opts.ignore = {".*"}
+      elseif options.variadic_inline_options[name] then
+         opts[name] = args
+      else
+         local full_name = name:gsub("_", " ")
+         local subs
+         name, subs = name:gsub("^no_", "")
+         local flag = subs == 0
+
+         if options.nullary_inline_options[name] then
+            if #args ~= 0 then
+               return nil, unexpected_num_args(full_name, args, 0)
+            end
+
+            opts[name] = flag
+         else
+            assert(limit_opts[name])
+
+            if flag then
+               if #args ~= 1 then
+                  return nil, unexpected_num_args(full_name, args, 1)
+               end
+
+               local value = tonumber(args[1])
+
+               if not value then
+                  return nil, ("inline option '%s' expects number as argument"):format(name)
+               end
+
+               opts[name] = value
+            else
+               if #args ~= 0 then
+                  return nil, unexpected_num_args(full_name, args, 0)
+               end
+
+               opts[name] = false
+            end
+         end
+      end
+   end
+
+   return opts
+end
+
+-- Parses comment contents, returns up to two `options` values (tables or "push" or "pop").
+-- On an invalid inline comment returns nil and an error message.
+local function parse_inline_comment(comment_contents)
+   local body = utils.after(utils.strip(comment_contents), "^luacheck:")
+
+   if not body then
+      return
+   end
+
+   local opts1, opts2
+
+   -- Remove comments in balanced parens.
+   body = utils.strip((body:gsub("%b()", " ")))
+   local after_push = body:match("^push%s+(.*)")
+
+   if after_push then
+      opts2 = "push"
+      body = after_push
+   elseif body == "push" or body == "pop" then
+      return body
+   end
+
+   local err_msg
+   opts1, err_msg = parse_options(body)
+   return opts1, err_msg or opts2
+end
+
+-- Returns an array of tables with column range info and an `options` field
+-- containing a table of options or "push" or "pop".
+-- Warns about invalid inline option comments.
+local function parse_inline_comments(chstate)
+   local res = {}
+
+   for _, comment in ipairs(chstate.comments) do
+      local opts1, opts2 = parse_inline_comment(comment.contents)
+
+      if opts1 then
+         table.insert(res, {
+            line = comment.line,
+            column = chstate:offset_to_column(comment.line, comment.offset),
+            end_column = chstate:offset_to_column(comment.line, comment.end_offset),
+            options = opts1
+         })
+
+         if opts2 then
+            table.insert(res, {
+               line = comment.line,
+               column = chstate:offset_to_column(comment.line, comment.offset),
+               end_column = chstate:offset_to_column(comment.line, comment.end_offset),
+               options = opts2
+            })
+         end
+      elseif opts2 then
+         chstate:warn_range("021", comment, {msg = opts2})
+      end
+   end
+
+   return res
+end
+
+-- Adds a table with `line`, `column`, and `options` fields to given array.
+-- For each function a table with `options` set to "push" for the function start
+-- and a talbe with `options` set to "pop" for the function end are added.
+local function add_function_boundaries(inline_options_and_boundaries, chstate)
+   for _, line in ipairs(chstate.top_line.lines) do
+      local fn_node = line.node
+
+      table.insert(inline_options_and_boundaries, {
+         line = fn_node.line,
+         column = chstate:offset_to_column(fn_node.line, fn_node.offset),
+         options = "push"
+      })
+
+      table.insert(inline_options_and_boundaries, {
+         line = fn_node.end_range.line,
+         column = chstate:offset_to_column(fn_node.end_range.line, fn_node.end_range.offset),
+         options = "pop"
+      })
+   end
+end
+
+local function get_order(t)
+   if t.options == "push" then
+      return 1
+   elseif t.options == "pop" then
+      return 3
+   else
+      return 2
+   end
+end
+
+local function options_and_boundaries_comparator(t1, t2)
+   if t1.line ~= t2.line then
+      return t1.line < t2.line
+   end
+
+   -- For options and boundaries on the same line, all pushes are applied before options before pops.
+   -- (Valid pops will be moved to the start of the next line later.)
+   local order1 = get_order(t1)
+   local order2 = get_order(t2)
+
+   if order1 ~= order2 then
+      return order1 < order2
+   else
+      return t1.column < t2.column
+   end
+end
+
+-- Applies bounadaries withing `inline_options_and_boundaries` to replace them with pop count
+-- instructions in the resulting array.
+-- Comments on lines with code are popped at the end of line.
+-- Warns about unpaired push and pop directives.
+local function apply_boundaries(chstate, inline_options_and_boundaries)
+   local res = {}
+   local res_last
+
+   -- While iterating over inline options and boundaries track push
+   -- boundaries that were not popped yet plus the number of options
+   -- that would be on the option stack after applying all already
+   -- processed option table pushes and pops.
+   local pushes = utils.Stack()
+   local push_option_counts = utils.Stack()
+   local option_count = 0
+
+   for _, item in ipairs(inline_options_and_boundaries) do
+      if item.options == "push" then
+         pushes:push(item)
+         push_option_counts:push(option_count)
+      elseif item.options == "pop" then
+         -- Function boundaries are implicit, don't allow inline options to pop
+         -- them, don't allow function boundaries to pop inline option pushes either.
+         -- Inline options boundaries have end_column, function boundaries don't.
+         if not pushes.top or (item.end_column and not pushes.top.end_column) then
+            -- Inline option pop against nothing or a function push, mark as unpaired.
+            chstate:warn_column_range("023", item)
+         else
+            if not item.end_column then
+               -- Function pop, remove any unpaired inline option pushes.
+               while pushes.top and pushes.top.end_column do
+                  chstate:warn_column_range("022", pushes.top)
+                  pushes:pop()
+                  push_option_counts:pop()
+               end
+            end
+
+            pushes:pop()
+            local prev_option_count = push_option_counts:pop()
+            local pop_count = option_count - prev_option_count
+
+            if pop_count > 0 then
+               -- Place the pop instruction at the start of the next line so that getting option stack
+               -- for a line amounts to applying both the pop instruction and the option push for the line.
+               local line = item.line + 1
+
+               -- Collapse with a previous table if it's on the same line. It can only be a pop count table.
+               if res_last and res_last.line == line then
+                  res_last.pop_count = res_last.pop_count + pop_count
+               else
+                  res_last = {
+                     line = line,
+                     pop_count = pop_count
+                  }
+
+                  table.insert(res, res_last)
+               end
+            end
+
+            -- Update option stack size for this pop.
+            option_count = prev_option_count
+         end
+      else
+         -- Inline options table. Check if there is a pop count table for this line already.
+         if res_last and res_last.line == item.line then
+            res_last.options = item.options
+            res_last.column = item.column
+            res_last.end_column = item.end_column
+         else
+            res_last = item
+            table.insert(res, item)
+         end
+
+         if chstate.code_lines[item.line] then
+            -- Inline comment on a line with some code, immediately pop it.
+            res_last = {
+               line = item.line + 1,
+               pop_count = 1
+            }
+            table.insert(res, res_last)
+         else
+            option_count = option_count + 1
+         end
+      end
+   end
+
+   -- Any remaining pushes are unpaired inline comments from the main chunk.
+   while pushes.top do
+      chstate:warn_column_range("022", pushes:pop())
+   end
+
+   return res
+end
+
+-- Warns about invalid inline options.
+-- Sets `chstate.inline_options` to an array of tables that describe the way inline option tables
+-- are pushed onto and popped from the option stack when iterating over lines.
+-- Each table has field `line` that the array is sorted by and also ether or both sets of fields:
+-- * `pop_count` - refers to a number of option tables that should be popped from the stack before processing
+--   warnings on this line.
+-- * `options`, `column`, `end_column` - refers to an option table that should be pushed onto the stack
+--   before processing warnings on this line but after popping tables if `pop_count` is present.
+function stage.run(chstate)
+   local inline_options_and_boundaries = parse_inline_comments(chstate)
+   add_function_boundaries(inline_options_and_boundaries, chstate)
+   table.sort(inline_options_and_boundaries, options_and_boundaries_comparator)
+   chstate.inline_options = apply_boundaries(chstate, inline_options_and_boundaries)
+end
+
+return stage
diff --git a/script/luacheck/stages/resolve_locals.lua b/script/luacheck/stages/resolve_locals.lua
new file mode 100755
index 000000000..c045609b0
--- /dev/null
+++ b/script/luacheck/stages/resolve_locals.lua
@@ -0,0 +1,216 @@
+local stage = {}
+
+-- The main part of analysis is connecting assignments to locals or upvalues
+-- with accesses that may use the assigned value.
+-- Accesses and assignments are split into two groups based on whether they happen
+-- in the closure that defines subject local variable (main assignment, main access)
+-- or in some nested closure (closure assignment, closure access).
+-- To avoid false positives, it's assumed that a closure may be called at any point
+-- starting from expression that creates it.
+-- Additionally, all operations on upvalues are considered in bulk, as in,
+-- when a closure is called, it's assumed that any subset of its upvalue assignments
+-- and accesses may happen, in any order.
+
+-- Assignments and accesses are connected based on whether they can reach each other.
+-- A main assignment is connected with a main access when the assignment can reach the access.
+-- A main assignment is connected with a closure access when the assignment can reach the closure creation
+-- or the closure creation can reach the assignment.
+-- A closure assignment is connected with a main access when the closure creation can reach the access.
+-- A closure assignment is connected with a closure access when either closure creation can reach the other one.
+
+-- To determine what flow graph nodes an assignment or a closure creation can reach,
+-- they are independently propagated along the graph.
+-- Closure creation propagation is not bounded.
+-- Main assignment propagation is bounded by entrance and exit conditions for each reached flow graph node.
+-- Entrance condition checks that target local variable is still in scope. If entrance condition fails,
+-- nothing in the node can refer to the variable, and the scope can't be reentered later.
+-- So, in this case, assignment does not reach the node, and propagation does not continue.
+-- Exit condition checks that target local variable is not overwritten by an assignment in the node.
+-- If it fails, the assignment still reaches the node (because all accesses in a node are evaluated before any
+-- assignments take effect), but propagation does not continue.
+
+local function register_value(values_per_var, var, value)
+   if not values_per_var[var] then
+      values_per_var[var] = {}
+   end
+
+   table.insert(values_per_var[var], value)
+end
+
+-- Called when assignment of `value` is connected to an access.
+-- `item` contains the access, and `line` contains the item.
+local function add_resolution(line, item, var, value, is_mutation)
+   register_value(item.used_values, var, value)
+   value[is_mutation and "mutated" or "used"] = true
+   value.using_lines[line] = true
+
+   if value.secondaries then
+      value.secondaries.used = true
+   end
+end
+
+-- Connects accesses in given items array with an assignment of `value`.
+-- `items` may be `nil` instead of empty.
+local function add_resolutions(line, items, var, value, is_mutation)
+   if not items then
+      return
+   end
+
+   for _, item in ipairs(items) do
+      add_resolution(line, item, var, value, is_mutation)
+   end
+end
+
+-- Connects all accesses (and mutations) in `access_line` with corresponding
+-- assignments in `set_line`.
+local function cross_resolve_closures(access_line, set_line)
+   for var, setting_items in pairs(set_line.set_upvalues) do
+      for _, setting_item in ipairs(setting_items) do
+         add_resolutions(access_line, access_line.accessed_upvalues[var],
+            var, setting_item.set_variables[var])
+         add_resolutions(access_line, access_line.mutated_upvalues[var],
+            var, setting_item.set_variables[var], true)
+      end
+   end
+end
+
+local function in_scope(var, index)
+   return (var.scope_start <= index) and (index <= var.scope_end)
+end
+
+-- Called when main assignment propagation reaches a line item.
+local function main_assignment_propagation_callback(line, index, item, var, value)
+   -- Check entrance condition.
+   if not in_scope(var, index) then
+      -- Assignment reaches the end of variable scope, so it can't be dominated by any assignment.
+      value.overwriting_item = false
+      return true
+   end
+
+   -- Assignment reaches this item, apply its effect.
+
+   -- Accesses (and mutations) of the variable can resolve to reaching assignment.
+   if item.accesses and item.accesses[var] then
+      add_resolution(line, item, var, value)
+   end
+
+   if item.mutations and item.mutations[var] then
+      add_resolution(line, item, var, value, true)
+   end
+
+   -- Accesses (and mutations) of the variable inside closures created in this item
+   -- can resolve to reaching assignment.
+   if item.lines then
+      for _, created_line in ipairs(item.lines) do
+         add_resolutions(created_line, created_line.accessed_upvalues[var], var, value)
+         add_resolutions(created_line, created_line.mutated_upvalues[var], var, value, true)
+      end
+   end
+
+   -- Check exit condition.
+   if item.set_variables and item.set_variables[var] then
+      if value.overwriting_item ~= false then
+         if value.overwriting_item and value.overwriting_item ~= item then
+            value.overwriting_item = false
+         else
+            value.overwriting_item = item
+         end
+      end
+
+      return true
+   end
+end
+
+-- Connects main assignments with main accesses and closure accesses in reachable closures.
+-- Additionally, sets `overwriting_item` field of values to an item with an assignment overwriting
+-- the value, but only if the overwriting is not avoidable (i.e. it's impossible to reach end of function
+-- from the first assignment without going through the second one). Otherwise value of the field may be
+-- `false` or `nil`.
+local function propagate_main_assignments(line)
+   for i, item in ipairs(line.items) do
+      if item.set_variables then
+         for var, value in pairs(item.set_variables) do
+            if var.line == line then
+               -- Assignments are not live at their own item, because assignments take effect only after all accesses
+               -- are evaluated. Items with assignments can't be jumps, so they have a single following item
+               -- with incremented index.
+               line:walk({}, i + 1, main_assignment_propagation_callback, var, value)
+            end
+         end
+      end
+   end
+end
+
+-- Called when closure creation propagation reaches a line item.
+local function closure_creation_propagation_callback(line, _, item, propagated_line)
+   if not item then
+      return true
+   end
+
+   -- Closure creation reaches this item, apply its effects.
+
+   -- Accesses (and mutations) of upvalues in the propagated closure
+   -- can resolve to assignments in the item.
+   if item.set_variables then
+      for var, value in pairs(item.set_variables) do
+         add_resolutions(propagated_line, propagated_line.accessed_upvalues[var], var, value)
+         add_resolutions(propagated_line, propagated_line.mutated_upvalues[var], var, value, true)
+      end
+   end
+
+   if item.lines then
+      for _, created_line in ipairs(item.lines) do
+         -- Accesses (and mutations) of upvalues in the propagated closure
+         -- can resolve to assignments in closures created in the item.
+         cross_resolve_closures(propagated_line, created_line)
+
+         -- Accesses (and mutations) of upvalues in closures created in the item
+         -- can resolve to assignments in the propagated closure.
+         cross_resolve_closures(created_line, propagated_line)
+      end
+   end
+
+   -- Accesses (and mutations) of locals in the item can resolve
+   -- to assignments in the propagated closure.
+   for var, setting_items in pairs(propagated_line.set_upvalues) do
+      if item.accesses and item.accesses[var] then
+         for _, setting_item in ipairs(setting_items) do
+            add_resolution(line, item, var, setting_item.set_variables[var])
+         end
+      end
+
+      if item.mutations and item.mutations[var] then
+         for _, setting_item in ipairs(setting_items) do
+            add_resolution(line, item, var, setting_item.set_variables[var], true)
+         end
+      end
+   end
+end
+
+-- Connects main assignments with closure accesses in reaching closures.
+-- Connects closure assignments with main accesses and with closure accesses in reachable closures.
+-- Connects closure accesses with closure assignments in reachable closures.
+local function propagate_closure_creations(line)
+   for i, item in ipairs(line.items) do
+      if item.lines then
+         for _, created_line in ipairs(item.lines) do
+            -- Closures are live at the item they are created, as they can be called immediately.
+            line:walk({}, i, closure_creation_propagation_callback, created_line)
+         end
+      end
+   end
+end
+
+local function analyze_line(line)
+   propagate_main_assignments(line)
+   propagate_closure_creations(line)
+end
+
+-- Finds reaching assignments for all local variable accesses.
+function stage.run(chstate)
+   for _, line in ipairs(chstate.lines) do
+      analyze_line(line)
+   end
+end
+
+return stage
diff --git a/script/luacheck/stages/unwrap_parens.lua b/script/luacheck/stages/unwrap_parens.lua
new file mode 100755
index 000000000..c4c860bf8
--- /dev/null
+++ b/script/luacheck/stages/unwrap_parens.lua
@@ -0,0 +1,52 @@
+local stage = {}
+
+-- Mutates an array of nodes and non-tables, unwrapping Paren nodes.
+-- If list_start is given, tail Paren is not unwrapped if it's unpacking and past list_start index.
+local function handle_nodes(nodes, list_start)
+   local num_nodes = #nodes
+
+   for index = 1, num_nodes do
+      local node = nodes[index]
+
+      if type(node) == "table" then
+         local tag = node.tag
+
+         if tag == "Table" or tag == "Return" then
+            handle_nodes(node, 1)
+         elseif tag == "Call" then
+            handle_nodes(node, 2)
+         elseif tag == "Invoke" then
+            handle_nodes(node, 3)
+         elseif tag == "Forin" then
+            handle_nodes(node[2], 1)
+            handle_nodes(node[3])
+         elseif tag == "Local" then
+            if node[2] then
+               handle_nodes(node[2])
+            end
+         elseif tag == "Set" then
+            handle_nodes(node[1])
+            handle_nodes(node[2], 1)
+         else
+            handle_nodes(node)
+
+            if tag == "Paren" and (not list_start or index < list_start or index ~= num_nodes) then
+               local inner_node = node[1]
+
+               if inner_node.tag ~= "Call" and inner_node.tag ~= "Invoke" and inner_node.tag ~= "Dots" then
+                  nodes[index] = inner_node
+               end
+            end
+         end
+      end
+   end
+end
+
+-- Mutates AST, unwrapping Paren nodes.
+-- Paren nodes are preserved only when they matter:
+-- at the ends of expression lists with potentially multi-value inner expressions.
+function stage.run(chstate)
+   handle_nodes(chstate.ast)
+end
+
+return stage
diff --git a/script/luacheck/standards.lua b/script/luacheck/standards.lua
new file mode 100755
index 000000000..f3d8bd654
--- /dev/null
+++ b/script/luacheck/standards.lua
@@ -0,0 +1,269 @@
+local standards = {}
+
+-- A standard (aka std) defines set of allowed globals, their fields,
+-- and whether they are mutable.
+--
+-- A standard can be in several formats. Internal (normalized) format
+-- is a tree. Each node defines a global or its field. Each node may have
+-- boolean `read_only` and `other_fields`, and may contain definitions
+-- of nested fields in `fields` subtable, which maps field names
+-- to definition tables. For example, standard defining globals
+-- of some Lua version may start like this:
+-- {
+--    -- Most globals are read-only by default.
+--    read_only = true,
+--    fields = {
+--       -- The tree can't be recursive, just allow everything for `_G`.
+--       _G = {other_fields = true, read_only = false},
+--       package = {
+--          fields = {
+--             -- `other_fields` is false by default, so that an empty table
+--             -- defines a field that can't be indexed further (a function in this case).
+--             loadlib = {},
+--             -- Allow doing everything with `package.loaded`.
+--             loaded = {other_fields = true, read_only = false},
+--             -- More fields here...
+--          }
+--       },
+--       -- More globals here...
+--    }
+-- }
+--
+-- A similar format is used to define standards in table form
+-- in config. There are two differences:
+-- first, top level table can have two fields, `globals` and `read_globals`,
+-- that map global names to definition tables. Default value of `read_only` field
+-- for the these tables depends on which table they come from (`true` for `read_globals`
+-- and `false` for `globals`). Additionally, all tables that map field or global names
+-- to definition tables may have non-string keys, their associated values are interpreted
+-- as names instead and their definition table allows indexing with any keys indefinitely.
+-- E.g. `{fields = {"foo"}}` is equivalent to `{fields = {foo = {other_fields = true}}}`.
+-- This feature makes it easier to create less strict standards that do not care about fields,
+-- to ease migration from the old format.
+--
+-- Additionally, there are some predefined named standards in `luacheck.builtin_standards` module.
+-- In config and inline options its possible to use their names as strings to refer to them.
+
+-- Validates an optional table mapping field names to field definitions or non-string keys to names.
+-- `index` is an optional string specifying position of the field table in the root table.
+-- Returns a true if the table is valid, false, an error message, and index of the table with the error otherwise.
+local function validate_fields(fields, is_root, index)
+   if fields == nil then
+      return true
+   end
+
+   local field_type = is_root and "global" or "field"
+
+   if type(fields) ~= "table" then
+      return false, ("%ss table expected, got %s"):format(field_type, type(fields)), index
+   end
+
+   for key, value in pairs(fields) do
+      if type(key) == "string" then
+         local new_index = (index or "") .. "." .. key
+
+         if type(value) ~= "table" then
+            return false, ("%s description table expected, got %s"):format(field_type, type(value)), new_index
+         end
+
+         if value.read_only ~= nil and type(value.read_only) ~= "boolean" then
+            local err = "invalid value of option 'read_only': boolean expected, got " .. type(value.read_only)
+            return false, err, new_index
+         end
+
+         if value.other_fields ~= nil and type(value.other_fields) ~= "boolean" then
+            local err = "invalid value of option 'other_fields': boolean expected, got " .. type(value.other_fields)
+            return false, err, new_index
+         end
+
+         local ok, err, err_index = validate_fields(value.fields, false, new_index .. ".fields")
+
+         if not ok then
+            return false, err, err_index
+         end
+      elseif type(value) ~= "string" then
+         local key_as_string = type(key) == "number" and ("%.20g"):format(key) or ("<%s>"):format(type(key))
+         local new_index = ("%s[%s]"):format(index or "", key_as_string)
+         return false, ("string expected as %s name, got %s"):format(field_type, type(value)), new_index
+      end
+   end
+
+   return true
+end
+
+-- Validates a field table.
+-- Returns true if the table is valid, false and an error message otherwise.
+function standards.validate_globals_table(globals_table)
+   local ok, err, err_index = validate_fields(globals_table, true)
+
+   if ok then
+      return true
+   end
+
+   local err_prefix = err_index and ("in field %s: "):format(err_index) or ""
+   return false, err_prefix .. err
+end
+
+-- Validates an std table in user-side format.
+-- Returns true if the table is valid, false and an error message otherwise.
+function standards.validate_std_table(std_table)
+   local ok, err, err_index = validate_fields(std_table.globals, true, ".globals")
+
+   if ok then
+      ok, err, err_index = validate_fields(std_table.read_globals, true, ".read_globals")
+   end
+
+   if ok then
+      return true
+   end
+
+   local err_prefix = ("in field %s: "):format(err_index)
+   return false, err_prefix .. err
+end
+
+local infinitely_indexable_def = {other_fields = true}
+
+local function add_fields(def, fields, overwrite, ignore_array_part, default_read_only)
+   if not fields then
+      return
+   end
+
+   for field_name, field_def in pairs(fields) do
+      if type(field_name) == "string" or not ignore_array_part then
+         if type(field_name) ~= "string" then
+            field_name = field_def
+            field_def = infinitely_indexable_def
+         end
+
+         if not def.fields then
+            def.fields = {}
+         end
+
+         if not def.fields[field_name] then
+            def.fields[field_name] = {}
+         end
+
+         local existing_field_def = def.fields[field_name]
+         local new_read_only = field_def.read_only
+
+         if new_read_only == nil then
+            new_read_only = default_read_only
+         end
+
+         if new_read_only ~= nil then
+            if overwrite or new_read_only == false then
+               existing_field_def.read_only = new_read_only
+            end
+         end
+
+         if field_def.other_fields ~= nil then
+            if overwrite or field_def.other_fields == true then
+               existing_field_def.other_fields = field_def.other_fields
+            end
+         end
+
+         add_fields(existing_field_def, field_def.fields, overwrite, false, nil)
+      end
+   end
+end
+
+-- Merges in an std table in user-side format.
+-- By default the new state of normalized std is a union of the standard tables being merged,
+-- e.g. if either table allows some field to be mutated, result should allow it, too.
+-- If `overwrite` is truthy, read-only statuses from the new std table overwrite existing values.
+-- If `ignore_top_array_part` is truthy, non-string keys in `globals` and `read_globals` tables
+-- in `std_table` are not processed.
+function standards.add_std_table(final_std, std_table, overwrite, ignore_top_array_part)
+   add_fields(final_std, std_table.globals, overwrite, ignore_top_array_part, false)
+   add_fields(final_std, std_table.read_globals, overwrite, ignore_top_array_part, true)
+end
+
+-- Overwrites or adds definition of a field with given read-only status and any nested keys.
+-- Field is specified as an array of field names.
+function standards.overwrite_field(final_std, field_names, read_only)
+   local field_def = final_std
+
+   for _, field_name in ipairs(field_names) do
+      if not field_def.fields then
+         field_def.fields = {}
+      end
+
+      if not field_def.fields[field_name] then
+         field_def.fields[field_name] = {read_only = read_only}
+      end
+
+      field_def = field_def.fields[field_name]
+   end
+
+   for key in pairs(field_def) do
+      field_def[key] = nil
+   end
+
+   field_def.read_only = read_only
+   field_def.other_fields = true
+end
+
+-- Removes definition of a field from a normalized std table.
+-- Field is specified as an array of field names.
+function standards.remove_field(final_std, field_names)
+   local field_def = final_std
+   local parent_def
+
+   for _, field_name in ipairs(field_names) do
+      parent_def = field_def
+
+      if not field_def.fields or not field_def.fields[field_name] then
+         -- The field wasn't defined in the first place.
+         return
+      end
+
+      field_def = field_def.fields[field_name]
+   end
+
+   if parent_def then
+      parent_def.fields[field_names[#field_names]] = nil
+   end
+end
+
+local function infer_deep_read_only_statuses(def, read_only)
+   local deep_read_only = not def.other_fields or read_only
+
+   if def.fields then
+      for _, field_def in pairs(def.fields) do
+         local field_read_only = read_only
+
+         if field_def.read_only ~= nil then
+            field_read_only = field_def.read_only
+         end
+
+         infer_deep_read_only_statuses(field_def, field_read_only)
+         deep_read_only = deep_read_only and field_read_only and field_def.deep_read_only
+      end
+   end
+
+   if deep_read_only then
+      def.deep_read_only = true
+   end
+end
+
+-- Finishes building a normalized std tables.
+-- Adds `deep_read_only` fields with `true` value to definition tables
+-- that do not have any writable fields, recursively.
+function standards.finalize(final_std)
+   infer_deep_read_only_statuses(final_std, true)
+end
+
+local empty = {}
+
+-- Returns a definition table containing empty fields with given names.
+function standards.def_fields(...)
+   local fields = {}
+
+   for _, field in ipairs({...}) do
+      fields[field] = empty
+   end
+
+   return {fields = fields}
+end
+
+return standards
diff --git a/script/luacheck/unicode.lua b/script/luacheck/unicode.lua
new file mode 100755
index 000000000..e861aece4
--- /dev/null
+++ b/script/luacheck/unicode.lua
@@ -0,0 +1,36 @@
+local unicode_printability_boundaries = require "luacheck.unicode_printability_boundaries"
+
+local unicode = {}
+
+-- unicode_printability_boundaries is an array of first codepoints of
+-- each continuous block of codepoints that are all printable or all not printable.
+
+function unicode.is_printable(codepoint)
+   -- Binary search for index of the first boundary less than or equal to given codepoint.
+   local floor_boundary_index
+
+   -- Target index is always in [begin_index..end_index).
+   local begin_index = 1
+   local end_index = #unicode_printability_boundaries + 1
+
+   while end_index - begin_index > 1 do
+      local mid_index = math.floor((begin_index + end_index) / 2)
+      local mid_codepoint = unicode_printability_boundaries[mid_index]
+
+      if codepoint < mid_codepoint then
+         end_index = mid_index
+      elseif codepoint > mid_codepoint then
+         begin_index = mid_index
+      else
+         floor_boundary_index = mid_index
+         break
+      end
+   end
+
+   floor_boundary_index = floor_boundary_index or begin_index
+   -- floor_boundary_index is the number of the block containing codepoint.
+   -- Printable and not printable blocks alternate and the first one is not printable (zero is not printable).
+   return floor_boundary_index % 2 == 0
+end
+
+return unicode
diff --git a/script/luacheck/unicode_printability_boundaries.lua b/script/luacheck/unicode_printability_boundaries.lua
new file mode 100755
index 000000000..d597a304b
--- /dev/null
+++ b/script/luacheck/unicode_printability_boundaries.lua
@@ -0,0 +1,2 @@
+-- Autogenerated using data from https://www.unicode.org/Public/11.0.0/ucd/UnicodeData.txt
+return {0,32,127,160,173,174,888,890,896,900,907,908,909,910,930,931,1328,1329,1367,1369,1419,1421,1424,1425,1480,1488,1515,1519,1525,1542,1564,1566,1757,1758,1806,1808,1867,1869,1970,1984,2043,2045,2094,2096,2111,2112,2140,2142,2143,2144,2155,2208,2229,2230,2238,2259,2274,2275,2436,2437,2445,2447,2449,2451,2473,2474,2481,2482,2483,2486,2490,2492,2501,2503,2505,2507,2511,2519,2520,2524,2526,2527,2532,2534,2559,2561,2564,2565,2571,2575,2577,2579,2601,2602,2609,2610,2612,2613,2615,2616,2618,2620,2621,2622,2627,2631,2633,2635,2638,2641,2642,2649,2653,2654,2655,2662,2679,2689,2692,2693,2702,2703,2706,2707,2729,2730,2737,2738,2740,2741,2746,2748,2758,2759,2762,2763,2766,2768,2769,2784,2788,2790,2802,2809,2816,2817,2820,2821,2829,2831,2833,2835,2857,2858,2865,2866,2868,2869,2874,2876,2885,2887,2889,2891,2894,2902,2904,2908,2910,2911,2916,2918,2936,2946,2948,2949,2955,2958,2961,2962,2966,2969,2971,2972,2973,2974,2976,2979,2981,2984,2987,2990,3002,3006,3011,3014,3017,3018,3022,3024,3025,3031,3032,3046,3067,3072,3085,3086,3089,3090,3113,3114,3130,3133,3141,3142,3145,3146,3150,3157,3159,3160,3163,3168,3172,3174,3184,3192,3213,3214,3217,3218,3241,3242,3252,3253,3258,3260,3269,3270,3273,3274,3278,3285,3287,3294,3295,3296,3300,3302,3312,3313,3315,3328,3332,3333,3341,3342,3345,3346,3397,3398,3401,3402,3408,3412,3428,3430,3456,3458,3460,3461,3479,3482,3506,3507,3516,3517,3518,3520,3527,3530,3531,3535,3541,3542,3543,3544,3552,3558,3568,3570,3573,3585,3643,3647,3676,3713,3715,3716,3717,3719,3721,3722,3723,3725,3726,3732,3736,3737,3744,3745,3748,3749,3750,3751,3752,3754,3756,3757,3770,3771,3774,3776,3781,3782,3783,3784,3790,3792,3802,3804,3808,3840,3912,3913,3949,3953,3992,3993,4029,4030,4045,4046,4059,4096,4294,4295,4296,4301,4302,4304,4681,4682,4686,4688,4695,4696,4697,4698,4702,4704,4745,4746,4750,4752,4785,4786,4790,4792,4799,4800,4801,4802,4806,4808,4823,4824,4881,4882,4886,4888,4955,4957,4989,4992,5018,5024,5110,5112,5118,5120,5789,5792,5881,5888,5901,5902,5909,5920,5943,5952,5972,5984,5997,5998,6001,6002,6004,6016,6110,6112,6122,6128,6138,6144,6158,6160,6170,6176,6265,6272,6315,6320,6390,6400,6431,6432,6444,6448,6460,6464,6465,6468,6510,6512,6517,6528,6572,6576,6602,6608,6619,6622,6684,6686,6751,6752,6781,6783,6794,6800,6810,6816,6830,6832,6847,6912,6988,6992,7037,7040,7156,7164,7224,7227,7242,7245,7305,7312,7355,7357,7368,7376,7418,7424,7674,7675,7958,7960,7966,7968,8006,8008,8014,8016,8024,8025,8026,8027,8028,8029,8030,8031,8062,8064,8117,8118,8133,8134,8148,8150,8156,8157,8176,8178,8181,8182,8191,8192,8203,8208,8232,8239,8288,8304,8306,8308,8335,8336,8349,8352,8384,8400,8433,8448,8588,8592,9255,9280,9291,9312,11124,11126,11158,11160,11209,11210,11263,11264,11311,11312,11359,11360,11508,11513,11558,11559,11560,11565,11566,11568,11624,11631,11633,11647,11671,11680,11687,11688,11695,11696,11703,11704,11711,11712,11719,11720,11727,11728,11735,11736,11743,11744,11855,11904,11930,11931,12020,12032,12246,12272,12284,12288,12352,12353,12439,12441,12544,12549,12592,12593,12687,12688,12731,12736,12772,12784,12831,12832,13055,13056,19894,19904,40944,40960,42125,42128,42183,42192,42540,42560,42744,42752,42938,42999,43052,43056,43066,43072,43128,43136,43206,43214,43226,43232,43348,43359,43389,43392,43470,43471,43482,43486,43519,43520,43575,43584,43598,43600,43610,43612,43715,43739,43767,43777,43783,43785,43791,43793,43799,43808,43815,43816,43823,43824,43878,43888,44014,44016,44026,44032,55204,55216,55239,55243,55292,63744,64110,64112,64218,64256,64263,64275,64280,64285,64311,64312,64317,64318,64319,64320,64322,64323,64325,64326,64450,64467,64832,64848,64912,64914,64968,65008,65022,65024,65050,65056,65107,65108,65127,65128,65132,65136,65141,65142,65277,65281,65471,65474,65480,65482,65488,65490,65496,65498,65501,65504,65511,65512,65519,65532,65534,65536,65548,65549,65575,65576,65595,65596,65598,65599,65614,65616,65630,65664,65787,65792,65795,65799,65844,65847,65935,65936,65948,65952,65953,66000,66046,66176,66205,66208,66257,66272,66300,66304,66340,66349,66379,66384,66427,66432,66462,66463,66500,66504,66518,66560,66718,66720,66730,66736,66772,66776,66812,66816,66856,66864,66916,66927,66928,67072,67383,67392,67414,67424,67432,67584,67590,67592,67593,67594,67638,67639,67641,67644,67645,67647,67670,67671,67743,67751,67760,67808,67827,67828,67830,67835,67868,67871,67898,67903,67904,67968,68024,68028,68048,68050,68100,68101,68103,68108,68116,68117,68120,68121,68150,68152,68155,68159,68169,68176,68185,68192,68256,68288,68327,68331,68343,68352,68406,68409,68438,68440,68467,68472,68498,68505,68509,68521,68528,68608,68681,68736,68787,68800,68851,68858,68904,68912,68922,69216,69247,69376,69416,69424,69466,69632,69710,69714,69744,69759,69821,69822,69826,69840,69865,69872,69882,69888,69941,69942,69959,69968,70007,70016,70094,70096,70112,70113,70133,70144,70162,70163,70207,70272,70279,70280,70281,70282,70286,70287,70302,70303,70314,70320,70379,70384,70394,70400,70404,70405,70413,70415,70417,70419,70441,70442,70449,70450,70452,70453,70458,70459,70469,70471,70473,70475,70478,70480,70481,70487,70488,70493,70500,70502,70509,70512,70517,70656,70746,70747,70748,70749,70751,70784,70856,70864,70874,71040,71094,71096,71134,71168,71237,71248,71258,71264,71277,71296,71352,71360,71370,71424,71451,71453,71468,71472,71488,71680,71740,71840,71923,71935,71936,72192,72264,72272,72324,72326,72355,72384,72441,72704,72713,72714,72759,72760,72774,72784,72813,72816,72848,72850,72872,72873,72887,72960,72967,72968,72970,72971,73015,73018,73019,73020,73022,73023,73032,73040,73050,73056,73062,73063,73065,73066,73103,73104,73106,73107,73113,73120,73130,73440,73465,73728,74650,74752,74863,74864,74869,74880,75076,77824,78895,82944,83527,92160,92729,92736,92767,92768,92778,92782,92784,92880,92910,92912,92918,92928,92998,93008,93018,93019,93026,93027,93048,93053,93072,93760,93851,93952,94021,94032,94079,94095,94112,94176,94178,94208,100338,100352,101107,110592,110879,110960,111356,113664,113771,113776,113789,113792,113801,113808,113818,113820,113824,118784,119030,119040,119079,119081,119155,119163,119273,119296,119366,119520,119540,119552,119639,119648,119673,119808,119893,119894,119965,119966,119968,119970,119971,119973,119975,119977,119981,119982,119994,119995,119996,119997,120004,120005,120070,120071,120075,120077,120085,120086,120093,120094,120122,120123,120127,120128,120133,120134,120135,120138,120145,120146,120486,120488,120780,120782,121484,121499,121504,121505,121520,122880,122887,122888,122905,122907,122914,122915,122917,122918,122923,124928,125125,125127,125143,125184,125259,125264,125274,125278,125280,126065,126133,126464,126468,126469,126496,126497,126499,126500,126501,126503,126504,126505,126515,126516,126520,126521,126522,126523,126524,126530,126531,126535,126536,126537,126538,126539,126540,126541,126544,126545,126547,126548,126549,126551,126552,126553,126554,126555,126556,126557,126558,126559,126560,126561,126563,126564,126565,126567,126571,126572,126579,126580,126584,126585,126589,126590,126591,126592,126602,126603,126620,126625,126628,126629,126634,126635,126652,126704,126706,126976,127020,127024,127124,127136,127151,127153,127168,127169,127184,127185,127222,127232,127245,127248,127340,127344,127405,127462,127491,127504,127548,127552,127561,127568,127570,127584,127590,127744,128725,128736,128749,128752,128762,128768,128884,128896,128985,129024,129036,129040,129096,129104,129114,129120,129160,129168,129198,129280,129292,129296,129343,129344,129393,129395,129399,129402,129403,129404,129443,129456,129466,129472,129475,129488,129536,129632,129646,131072,173783,173824,177973,177984,178206,178208,183970,183984,191457,194560,195102,917760,918000,}
diff --git a/script/luacheck/utils.lua b/script/luacheck/utils.lua
new file mode 100755
index 000000000..907c62975
--- /dev/null
+++ b/script/luacheck/utils.lua
@@ -0,0 +1,377 @@
+-- luacheck: push compat
+local unpack = table.unpack or unpack
+local pack = table.pack or function(...) return {n = select("#", ...), ...} end
+-- luacheck: pop
+
+local utils = {}
+
+utils.dir_sep = package.config:sub(1,1)
+utils.is_windows = utils.dir_sep == "\\"
+
+local bom = "\239\187\191"
+
+-- Returns all contents of file (path or file handler) or nil + error message.
+function utils.read_file(file)
+   local handler
+
+   if type(file) == "string" then
+      local open_err
+      handler, open_err = io.open(file, "rb")
+
+      if not handler then
+         open_err = utils.unprefix(open_err, file .. ": ")
+         return nil, "couldn't read: " .. open_err
+      end
+   else
+      handler = file
+   end
+
+   local res, read_err = handler:read("*a")
+   handler:close()
+
+   if not res then
+      return nil, "couldn't read: " .. read_err
+   end
+
+   -- Use :len() instead of # operator because in some environments
+   -- string library is patched to handle UTF.
+   if res:sub(1, bom:len()) == bom then
+      res = res:sub(bom:len() + 1)
+   end
+
+   return res
+end
+
+-- luacheck: push
+-- luacheck: compat
+if _VERSION:find "5.1" then
+   -- Loads Lua source string in an environment, returns function or nil, error.
+   function utils.load(src, env, chunkname)
+      local func, err = loadstring(src, chunkname)
+
+      if func then
+         if env then
+            setfenv(func, env)
+         end
+
+         return func
+      else
+         return nil, err
+      end
+   end
+else
+   -- Loads Lua source string in an environment, returns function or nil, error.
+   function utils.load(src, env, chunkname)
+      return load(src, chunkname, "t", env or _ENV)
+   end
+end
+-- luacheck: pop
+
+-- Loads config containing assignments to global variables from path.
+-- Returns config table and return value of config or nil and error type
+-- ("I/O" or "syntax" or "runtime") and error message.
+function utils.load_config(path, env)
+   env = env or {}
+   local src, read_err = utils.read_file(path)
+
+   if not src then
+      return nil, "I/O", read_err
+   end
+
+   local func, load_err = utils.load(src, env, "chunk")
+
+   if not func then
+      return nil, "syntax", "line " .. utils.unprefix(load_err, "[string \"chunk\"]:")
+   end
+
+   local ok, res = pcall(func)
+
+   if not ok then
+      return nil, "runtime", "line " .. utils.unprefix(res, "[string \"chunk\"]:")
+   end
+
+   return env, res
+end
+
+function utils.array_to_set(array)
+   local set = {}
+
+   for index, value in ipairs(array) do
+      set[value] = index
+   end
+
+   return set
+end
+
+function utils.concat_arrays(array)
+   local res = {}
+
+   for _, subarray in ipairs(array) do
+      for _, item in ipairs(subarray) do
+         table.insert(res, item)
+      end
+   end
+
+   return res
+end
+
+function utils.update(t1, t2)
+   for k, v in pairs(t2) do
+      t1[k] = v
+   end
+
+   return t1
+end
+
+local class_metatable = {}
+
+function class_metatable.__call(class, ...)
+   local obj = setmetatable({}, class)
+
+   if class.__init then
+      local init_returns = pack(class.__init(obj, ...))
+
+      if init_returns.n > 0 then
+         return unpack(init_returns, 1, init_returns.n)
+      end
+   end
+
+   return obj
+end
+
+function utils.class()
+   local class = setmetatable({}, class_metatable)
+   class.__index = class
+   return class
+end
+
+function utils.is_instance(object, class)
+   return rawequal(debug.getmetatable(object), class)
+end
+
+utils.Stack = utils.class()
+
+function utils.Stack:__init()
+   self.size = 0
+end
+
+function utils.Stack:push(value)
+   self.size = self.size + 1
+   self[self.size] = value
+   self.top = value
+end
+
+function utils.Stack:pop()
+   local value = self[self.size]
+   self[self.size] = nil
+   self.size = self.size - 1
+   self.top = self[self.size]
+   return value
+end
+
+local ErrorWrapper = utils.class()
+
+function ErrorWrapper:__init(err, traceback)
+   self.err = err
+   self.traceback = traceback
+end
+
+function ErrorWrapper:__tostring()
+   return tostring(self.err) .. "\n" .. self.traceback
+end
+
+local function error_handler(err)
+   if utils.is_instance(err, ErrorWrapper) then
+      return err
+   else
+      return ErrorWrapper(err, debug.traceback())
+   end
+end
+
+-- Like pcall, but wraps errors in {err = err, traceback = traceback}
+-- tables unless already wrapped.
+function utils.try(f, ...)
+   local args = {...}
+   local num_args = select("#", ...)
+
+   local function task()
+      return f(unpack(args, 1, num_args))
+   end
+
+   return xpcall(task, error_handler)
+end
+
+local function ripairs_iterator(array, i)
+   if i == 1 then
+      return nil
+   else
+      i = i - 1
+      return i, array[i]
+   end
+end
+
+function utils.ripairs(array)
+   return ripairs_iterator, array, #array + 1
+end
+
+function utils.sorted_pairs(t)
+   local keys = {}
+
+   for key in pairs(t) do
+      table.insert(keys, key)
+   end
+
+   table.sort(keys)
+
+   local index = 1
+
+   return function()
+      local key = keys[index]
+
+      if key == nil then
+         return
+      end
+
+      index = index + 1
+
+      return key, t[key]
+   end
+end
+
+function utils.unprefix(str, prefix)
+   if str:sub(1, #prefix) == prefix then
+      return str:sub(#prefix + 1)
+   else
+      return str
+   end
+end
+
+function utils.after(str, pattern)
+   local _, last_matched_index = str:find(pattern)
+
+   if last_matched_index then
+      return str:sub(last_matched_index + 1)
+   end
+end
+
+function utils.strip(str)
+   local _, last_start_space = str:find("^%s*")
+   local first_end_space = str:find("%s*$")
+   return str:sub(last_start_space + 1, first_end_space - 1)
+end
+
+-- `sep` must be nil or a single character. Behaves like python's `str.split`.
+function utils.split(str, sep)
+   local parts = {}
+   local pattern
+
+   if sep then
+      pattern = sep .. "([^" .. sep .. "]*)"
+      str = sep .. str
+   else
+      pattern = "%S+"
+   end
+
+   for part in str:gmatch(pattern) do
+      table.insert(parts, part)
+   end
+
+   return parts
+end
+
+utils.InvalidPatternError = utils.class()
+
+function utils.InvalidPatternError:__init(err, pattern)
+   self.err = err
+   self.pattern = pattern
+end
+
+function utils.InvalidPatternError:__tostring()
+   return self.err
+end
+
+-- Behaves like string.match, except it normally returns boolean and
+-- throws an instance of utils.InvalidPatternError on invalid pattern.
+-- The error object turns into original error when tostring is used on it,
+-- to ensure behaviour is predictable when luacheck is used as a module.
+function utils.pmatch(str, pattern)
+   assert(type(str) == "string")
+   assert(type(pattern) == "string")
+
+   local ok, res = pcall(string.match, str, pattern)
+
+   if not ok then
+      error(utils.InvalidPatternError(res, pattern), 0)
+   else
+      return not not res
+   end
+end
+
+-- Maps func over array.
+function utils.map(func, array)
+   local res = {}
+
+   for i, item in ipairs(array) do
+      res[i] = func(item)
+   end
+
+   return res
+end
+
+-- Returns validator checking type.
+function utils.has_type(type_)
+   return function(x)
+      if type(x) == type_ then
+         return true
+      else
+         return false, ("%s expected, got %s"):format(type_, type(x))
+      end
+   end
+end
+
+-- Returns validator checking type and allowing false.
+function utils.has_type_or_false(type_)
+   return function(x)
+      if type(x) == type_ then
+         return true
+      elseif type(x) == "boolean" then
+         if x then
+            return false, ("%s or false expected, got true"):format(type_)
+         else
+            return true
+         end
+      else
+         return false, ("%s or false expected, got %s"):format(type_, type(x))
+      end
+   end
+end
+
+-- Returns validator checking two type possibilities.
+function utils.has_either_type(type1, type2)
+   return function(x)
+      if type(x) == type1 or type(x) == type2 then
+         return true
+      else
+         return false, ("%s or %s expected, got %s"):format(type1, type2, type(x))
+      end
+   end
+end
+
+-- Returns validator checking that value is an array with elements of type.
+function utils.array_of(type_)
+   return function(x)
+      if type(x) ~= "table" then
+         return false, ("array of %ss expected, got %s"):format(type_, type(x))
+      end
+
+      for index, item in ipairs(x) do
+         if type(item) ~= type_ then
+            return false, ("array of %ss expected, got %s at index [%d]"):format(type_, type(item), index)
+         end
+      end
+
+      return true
+   end
+end
+
+return utils
diff --git a/script/luacheck/vendor/sha1/LICENSE b/script/luacheck/vendor/sha1/LICENSE
new file mode 100755
index 000000000..0c4a9516b
--- /dev/null
+++ b/script/luacheck/vendor/sha1/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl
+Copyright (c) 2018 Peter Melnichenko
+
+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.
diff --git a/script/luacheck/vendor/sha1/bit32_ops.lua b/script/luacheck/vendor/sha1/bit32_ops.lua
new file mode 100755
index 000000000..344b0f364
--- /dev/null
+++ b/script/luacheck/vendor/sha1/bit32_ops.lua
@@ -0,0 +1,24 @@
+local bit32 = require "bit32"
+
+local ops = {}
+
+local band = bit32.band
+local bor = bit32.bor
+local bxor = bit32.bxor
+
+ops.uint32_lrot = bit32.lrotate
+ops.byte_xor = bxor
+ops.uint32_xor_3 = bxor
+ops.uint32_xor_4 = bxor
+
+function ops.uint32_ternary(a, b, c)
+   -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+   return bxor(c, band(a, bxor(b, c)))
+end
+
+function ops.uint32_majority(a, b, c)
+   -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+   return bor(band(a, bor(b, c)), band(b, c))
+end
+
+return ops
diff --git a/script/luacheck/vendor/sha1/bit_ops.lua b/script/luacheck/vendor/sha1/bit_ops.lua
new file mode 100755
index 000000000..5fbaf056f
--- /dev/null
+++ b/script/luacheck/vendor/sha1/bit_ops.lua
@@ -0,0 +1,24 @@
+local bit = require "bit"
+
+local ops = {}
+
+local band = bit.band
+local bor = bit.bor
+local bxor = bit.bxor
+
+ops.uint32_lrot = bit.rol
+ops.byte_xor = bxor
+ops.uint32_xor_3 = bxor
+ops.uint32_xor_4 = bxor
+
+function ops.uint32_ternary(a, b, c)
+   -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+   return bxor(c, band(a, bxor(b, c)))
+end
+
+function ops.uint32_majority(a, b, c)
+   -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+   return bor(band(a, bor(b, c)), band(b, c))
+end
+
+return ops
diff --git a/script/luacheck/vendor/sha1/common.lua b/script/luacheck/vendor/sha1/common.lua
new file mode 100755
index 000000000..c31ffef46
--- /dev/null
+++ b/script/luacheck/vendor/sha1/common.lua
@@ -0,0 +1,20 @@
+local common = {}
+
+-- Merges four bytes into a uint32 number.
+function common.bytes_to_uint32(a, b, c, d)
+   return a * 0x1000000 + b * 0x10000 + c * 0x100 + d
+end
+
+-- Splits a uint32 number into four bytes.
+function common.uint32_to_bytes(a)
+   local a4 = a % 256
+   a = (a - a4) / 256
+   local a3 = a % 256
+   a = (a - a3) / 256
+   local a2 = a % 256
+   local a1 = (a - a2) / 256
+   return a1, a2, a3, a4
+end
+
+
+return common
diff --git a/script/luacheck/vendor/sha1/init.lua b/script/luacheck/vendor/sha1/init.lua
new file mode 100755
index 000000000..49f95bee0
--- /dev/null
+++ b/script/luacheck/vendor/sha1/init.lua
@@ -0,0 +1,195 @@
+local common = require "luacheck.vendor.sha1.common"
+
+local sha1 = {
+   -- Meta fields retained for compatibility.
+   _VERSION     = "sha.lua 0.6.0",
+   _URL         = "https://github.com/mpeterv/sha1",
+   _DESCRIPTION = [[
+SHA-1 secure hash and HMAC-SHA1 signature computation in Lua,
+using bit and bit32 modules and Lua 5.3 operators when available
+and falling back to a pure Lua implementation on Lua 5.1.
+Based on code orignally by Jeffrey Friedl and modified by
+Eike Decker and Enrique García Cota.]],
+   _LICENSE = [[
+MIT LICENSE
+
+Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl
+Copyright (c) 2018 Peter Melnichenko
+
+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.]]
+}
+
+sha1.version = "0.6.0"
+
+local function choose_ops()
+   if _VERSION:find("5%.3") then
+      return "lua53_ops"
+   elseif pcall(require, "bit") then
+      return "bit_ops"
+   elseif pcall(require, "bit32") then
+      return "bit32_ops"
+   else
+      return "pure_lua_ops"
+   end
+end
+
+local ops = require("luacheck.vendor.sha1." .. choose_ops())
+local uint32_lrot = ops.uint32_lrot
+local byte_xor = ops.byte_xor
+local uint32_xor_3 = ops.uint32_xor_3
+local uint32_xor_4 = ops.uint32_xor_4
+local uint32_ternary = ops.uint32_ternary
+local uint32_majority = ops.uint32_majority
+
+local bytes_to_uint32 = common.bytes_to_uint32
+local uint32_to_bytes = common.uint32_to_bytes
+
+local sbyte = string.byte
+local schar = string.char
+local sformat = string.format
+local srep = string.rep
+
+local function hex_to_binary(hex)
+   return (hex:gsub("..", function(hexval)
+      return schar(tonumber(hexval, 16))
+   end))
+end
+
+-- Calculates SHA1 for a string, returns it encoded as 40 hexadecimal digits.
+function sha1.sha1(str)
+   -- Input preprocessing.
+   -- First, append a `1` bit and seven `0` bits.
+   local first_append = schar(0x80)
+
+   -- Next, append some zero bytes to make the length of the final message a multiple of 64.
+   -- Eight more bytes will be added next.
+   local non_zero_message_bytes = #str + 1 + 8
+   local second_append = srep(schar(0), -non_zero_message_bytes % 64)
+
+   -- Finally, append the length of the original message in bits as a 64-bit number.
+   -- Assume that it fits into the lower 32 bits.
+   local third_append = schar(0, 0, 0, 0, uint32_to_bytes(#str * 8))
+
+   str = str .. first_append .. second_append .. third_append
+   assert(#str % 64 == 0)
+
+   -- Initialize hash value.
+   local h0 = 0x67452301
+   local h1 = 0xEFCDAB89
+   local h2 = 0x98BADCFE
+   local h3 = 0x10325476
+   local h4 = 0xC3D2E1F0
+
+   local w = {}
+
+   -- Process the input in successive 64-byte chunks.
+   for chunk_start = 1, #str, 64 do
+      -- Load the chunk into W[0..15] as uint32 numbers.
+      local uint32_start = chunk_start
+
+      for i = 0, 15 do
+         w[i] = bytes_to_uint32(sbyte(str, uint32_start, uint32_start + 3))
+         uint32_start = uint32_start + 4
+      end
+
+      -- Extend the input vector.
+      for i = 16, 79 do
+         w[i] = uint32_lrot(uint32_xor_4(w[i - 3], w[i - 8], w[i - 14], w[i - 16]), 1)
+      end
+
+      -- Initialize hash value for this chunk.
+      local a = h0
+      local b = h1
+      local c = h2
+      local d = h3
+      local e = h4
+
+      -- Main loop.
+      for i = 0, 79 do
+         local f
+         local k
+
+         if i <= 19 then
+            f = uint32_ternary(b, c, d)
+            k = 0x5A827999
+         elseif i <= 39 then
+            f = uint32_xor_3(b, c, d)
+            k = 0x6ED9EBA1
+         elseif i <= 59 then
+            f = uint32_majority(b, c, d)
+            k = 0x8F1BBCDC
+         else
+            f = uint32_xor_3(b, c, d)
+            k = 0xCA62C1D6
+         end
+
+         local temp = (uint32_lrot(a, 5) + f + e + k + w[i]) % 0x100000000
+         e = d
+         d = c
+         c = uint32_lrot(b, 30)
+         b = a
+         a = temp
+      end
+
+      -- Add this chunk's hash to result so far.
+      h0 = (h0 + a) % 0x100000000
+      h1 = (h1 + b) % 0x100000000
+      h2 = (h2 + c) % 0x100000000
+      h3 = (h3 + d) % 0x100000000
+      h4 = (h4 + e) % 0x100000000
+   end
+
+   return sformat("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4)
+end
+
+function sha1.binary(str)
+   return hex_to_binary(sha1.sha1(str))
+end
+
+-- Precalculate replacement tables.
+local xor_with_0x5c = {}
+local xor_with_0x36 = {}
+
+for i = 0, 0xff do
+   xor_with_0x5c[schar(i)] = schar(byte_xor(0x5c, i))
+   xor_with_0x36[schar(i)] = schar(byte_xor(0x36, i))
+end
+
+-- 512 bits.
+local BLOCK_SIZE = 64
+
+function sha1.hmac(key, text)
+   if #key > BLOCK_SIZE then
+      key = sha1.binary(key)
+   end
+
+   local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. srep(schar(0x36), BLOCK_SIZE - #key)
+   local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. srep(schar(0x5c), BLOCK_SIZE - #key)
+
+   return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
+end
+
+function sha1.hmac_binary(key, text)
+   return hex_to_binary(sha1.hmac(key, text))
+end
+
+setmetatable(sha1, {__call = function(_, str) return sha1.sha1(str) end})
+
+return sha1
diff --git a/script/luacheck/vendor/sha1/lua53_ops.lua b/script/luacheck/vendor/sha1/lua53_ops.lua
new file mode 100755
index 000000000..d5312e6b5
--- /dev/null
+++ b/script/luacheck/vendor/sha1/lua53_ops.lua
@@ -0,0 +1,29 @@
+local ops = {}
+
+function ops.uint32_lrot(a, bits)
+   return ((a << bits) & 0xFFFFFFFF) | (a >> (32 - bits))
+end
+
+function ops.byte_xor(a, b)
+   return a ~ b
+end
+
+function ops.uint32_xor_3(a, b, c)
+   return a ~ b ~ c
+end
+
+function ops.uint32_xor_4(a, b, c, d)
+   return a ~ b ~ c ~ d
+end
+
+function ops.uint32_ternary(a, b, c)
+   -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+   return c ~ (a & (b ~ c))
+end
+
+function ops.uint32_majority(a, b, c)
+   -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+   return (a & (b | c)) | (b & c)
+end
+
+return ops
diff --git a/script/luacheck/vendor/sha1/pure_lua_ops.lua b/script/luacheck/vendor/sha1/pure_lua_ops.lua
new file mode 100755
index 000000000..8f255cb79
--- /dev/null
+++ b/script/luacheck/vendor/sha1/pure_lua_ops.lua
@@ -0,0 +1,144 @@
+local common = require "luacheck.vendor.sha1.common"
+
+local ops = {}
+
+local bytes_to_uint32 = common.bytes_to_uint32
+local uint32_to_bytes = common.uint32_to_bytes
+
+function ops.uint32_lrot(a, bits)
+   local power = 2 ^ bits
+   local inv_power = 0x100000000 / power
+   local lower_bits = a % inv_power
+   return (lower_bits * power) + ((a - lower_bits) / inv_power)
+end
+
+-- Build caches for bitwise `and` and `xor` over bytes to speed up uint32 operations.
+-- Building the cache by simply applying these operators over all pairs is too slow and
+-- duplicates a lot of work over different bits of inputs.
+-- Instead, when building a cache over bytes, for each pair of bytes split both arguments
+-- into two 4-bit numbers, calculate values over these two halves, then join the results into a byte again.
+-- While there are 256 * 256 = 65536 pairs of bytes, there are only 16 * 16 = 256 pairs
+-- of 4-bit numbers, so that building an 8-bit cache given a 4-bit cache is rather efficient.
+-- The same logic is applied recursively to make a 4-bit cache from a 2-bit cache and a 2-bit
+-- cache from a 1-bit cache, which is calculated given the 1-bit version of the operator.
+
+-- Returns a cache containing all values of a bitwise operator over numbers with given number of bits,
+-- given an operator over single bits.
+-- Value of `op(a, b)` is stored in `cache[a * (2 ^ bits) + b]`.
+local function make_op_cache(bit_op, bits)
+   if bits == 1 then
+      return {[0] = bit_op(0, 0), bit_op(0, 1), bit_op(1, 0), bit_op(1, 1)}
+   end
+
+   local half_bits = bits / 2
+   local size = 2 ^ bits
+   local half_size = 2 ^ half_bits
+   local half_cache = make_op_cache(bit_op, half_bits)
+
+   local cache = {}
+
+   -- The implementation used is an optimized version of the following reference one,
+   -- with intermediate calculations reused and moved to the outermost loop possible.
+   -- It's possible to reorder the loops and move the calculation of one of the
+   -- half-results one level up, but then the cache is not filled in a proper array order
+   -- and its access performance suffers.
+
+   -- for a1 = 0, half_size - 1 do
+   --    for a2 = 0, half_size - 1 do
+   --       for b1 = 0, half_size - 1 do
+   --          for b2 = 0, half_size - 1 do
+   --             local a = a1 * half_size + a2
+   --             local b = b1 * half_size + b2
+   --             local v1 = half_cache[a1 * half_size + b1]
+   --             local v2 = half_cache[a2 * half_size + b2]
+   --             local v = v1 * half_size + v2
+   --             cache[a * size + b] = v
+   --          end
+   --       end
+   --    end
+   -- end
+
+   for a1 = 0, half_size - 1 do
+      local a1_half_size = a1 * half_size
+
+      for a2 = 0, half_size - 1 do
+         local a2_size = a2 * half_size
+         local a_size = (a1_half_size + a2) * size
+
+         for b1 = 0, half_size - 1 do
+            local a_size_plus_b1_half_size = a_size + b1 * half_size
+            local v1_half_size = half_cache[a1_half_size + b1] * half_size
+
+            for b2 = 0, half_size - 1 do
+               cache[a_size_plus_b1_half_size + b2] = v1_half_size + half_cache[a2_size + b2]
+            end
+         end
+      end
+   end
+
+   return cache
+end
+
+local byte_and_cache = make_op_cache(function(a, b) return a * b end, 8)
+local byte_xor_cache = make_op_cache(function(a, b) return a == b and 0 or 1 end, 8)
+
+function ops.byte_xor(a, b)
+   return byte_xor_cache[a * 256 + b]
+end
+
+function ops.uint32_xor_3(a, b, c)
+   local a1, a2, a3, a4 = uint32_to_bytes(a)
+   local b1, b2, b3, b4 = uint32_to_bytes(b)
+   local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+   return bytes_to_uint32(
+      byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + c1]],
+      byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + c2]],
+      byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + c3]],
+      byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + c4]]
+   )
+end
+
+function ops.uint32_xor_4(a, b, c, d)
+   local a1, a2, a3, a4 = uint32_to_bytes(a)
+   local b1, b2, b3, b4 = uint32_to_bytes(b)
+   local c1, c2, c3, c4 = uint32_to_bytes(c)
+   local d1, d2, d3, d4 = uint32_to_bytes(d)
+
+   return bytes_to_uint32(
+      byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + byte_xor_cache[c1 * 256 + d1]]],
+      byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + byte_xor_cache[c2 * 256 + d2]]],
+      byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + byte_xor_cache[c3 * 256 + d3]]],
+      byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + byte_xor_cache[c4 * 256 + d4]]]
+   )
+end
+
+function ops.uint32_ternary(a, b, c)
+   local a1, a2, a3, a4 = uint32_to_bytes(a)
+   local b1, b2, b3, b4 = uint32_to_bytes(b)
+   local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+   -- (a & b) + (~a & c) has less bitwise operations than (a & b) | (~a & c).
+   return bytes_to_uint32(
+      byte_and_cache[b1 * 256 + a1] + byte_and_cache[c1 * 256 + 255 - a1],
+      byte_and_cache[b2 * 256 + a2] + byte_and_cache[c2 * 256 + 255 - a2],
+      byte_and_cache[b3 * 256 + a3] + byte_and_cache[c3 * 256 + 255 - a3],
+      byte_and_cache[b4 * 256 + a4] + byte_and_cache[c4 * 256 + 255 - a4]
+   )
+end
+
+function ops.uint32_majority(a, b, c)
+   local a1, a2, a3, a4 = uint32_to_bytes(a)
+   local b1, b2, b3, b4 = uint32_to_bytes(b)
+   local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+   -- (a & b) + (c & (a ~ b)) has less bitwise operations than (a & b) | (a & c) | (b & c).
+   return bytes_to_uint32(
+      byte_and_cache[a1 * 256 + b1] + byte_and_cache[c1 * 256 + byte_xor_cache[a1 * 256 + b1]],
+      byte_and_cache[a2 * 256 + b2] + byte_and_cache[c2 * 256 + byte_xor_cache[a2 * 256 + b2]],
+      byte_and_cache[a3 * 256 + b3] + byte_and_cache[c3 * 256 + byte_xor_cache[a3 * 256 + b3]],
+      byte_and_cache[a4 * 256 + b4] + byte_and_cache[c4 * 256 + byte_xor_cache[a4 * 256 + b4]]
+   )
+end
+
+return ops
diff --git a/script/luacheck/version.lua b/script/luacheck/version.lua
new file mode 100755
index 000000000..f4af858b2
--- /dev/null
+++ b/script/luacheck/version.lua
@@ -0,0 +1,36 @@
+local argparse = require "argparse"
+local lfs = require "lfs"
+local luacheck = require "luacheck"
+local multithreading = require "luacheck.multithreading"
+local utils = require "luacheck.utils"
+
+local version = {}
+
+version.luacheck = luacheck._VERSION
+
+if rawget(_G, "jit") then
+   version.lua = rawget(_G, "jit").version
+elseif _VERSION:find("^Lua ") then
+   version.lua = "PUC-Rio " .. _VERSION
+else
+   version.lua = _VERSION
+end
+
+version.argparse = argparse.version
+
+version.lfs = utils.unprefix(lfs._VERSION, "LuaFileSystem ")
+
+if multithreading.has_lanes then
+   version.lanes = multithreading.lanes.ABOUT.version
+else
+   version.lanes = "Not found"
+end
+
+version.string = ([[
+Luacheck: %s
+Lua: %s
+Argparse: %s
+LuaFileSystem: %s
+LuaLanes: %s]]):format(version.luacheck, version.lua, version.argparse, version.lfs, version.lanes)
+
+return version
diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua
index ceb42f8b8..fac07d985 100644
--- a/script/proto/diagnostic.lua
+++ b/script/proto/diagnostic.lua
@@ -39,164 +39,172 @@ function m.register(names)
 end
 
 m.register {
-    'unused-local',
-    'unused-function',
-    'unused-label',
-    'unused-vararg',
-    'trailing-space',
-    'redundant-return',
-    'empty-block',
-    'code-after-break',
-    'unreachable-code',
+    'luacheck',
 } {
-    group    = 'unused',
+    group    = 'luacheck',
     severity = 'Hint',
-    status   = 'Opened',
-}
-
-m.register {
-    'redundant-value',
-    'unbalanced-assignments',
-    'redundant-parameter',
-    'missing-parameter',
-    'missing-return-value',
-    'redundant-return-value',
-    'missing-return',
-} {
-    group    = 'unbalanced',
-    severity = 'Warning',
     status   = 'Any',
 }
 
-m.register {
-    'need-check-nil',
-    'undefined-field',
-    'cast-local-type',
-    'assign-type-mismatch',
-    'param-type-mismatch',
-    'cast-type-mismatch',
-    'return-type-mismatch',
-} {
-    group    = 'type-check',
-    severity = 'Warning',
-    status   = 'Opened',
-}
+-- m.register {
+--     'unused-local',
+--     'unused-function',
+--     'unused-label',
+--     'unused-vararg',
+--     'trailing-space',
+--     'redundant-return',
+--     'empty-block',
+--     'code-after-break',
+--     'unreachable-code',
+-- } {
+--     group    = 'unused',
+--     severity = 'Hint',
+--     status   = 'Opened',
+-- }
 
-m.register {
-    'duplicate-doc-alias',
-    'undefined-doc-class',
-    'undefined-doc-name',
-    'circle-doc-class',
-    'undefined-doc-param',
-    'duplicate-doc-param',
-    'doc-field-no-class',
-    'duplicate-doc-field',
-    'unknown-diag-code',
-    'unknown-cast-variable',
-    'unknown-operator',
-} {
-    group    = 'luadoc',
-    severity = 'Warning',
-    status   = 'Any',
-}
+-- m.register {
+--     'redundant-value',
+--     'unbalanced-assignments',
+--     'redundant-parameter',
+--     'missing-parameter',
+--     'missing-return-value',
+--     'redundant-return-value',
+--     'missing-return',
+-- } {
+--     group    = 'unbalanced',
+--     severity = 'Warning',
+--     status   = 'Any',
+-- }
 
-m.register {
-    'codestyle-check'
-} {
-    group    = 'codestyle',
-    severity = 'Warning',
-    status   = 'None',
-}
+-- m.register {
+--     'need-check-nil',
+--     'undefined-field',
+--     'cast-local-type',
+--     'assign-type-mismatch',
+--     'param-type-mismatch',
+--     'cast-type-mismatch',
+--     'return-type-mismatch',
+-- } {
+--     group    = 'type-check',
+--     severity = 'Warning',
+--     status   = 'Opened',
+-- }
 
-m.register {
-    'spell-check'
-} {
-    group    = 'codestyle',
-    severity = 'Information',
-    status   = 'None',
-}
+-- m.register {
+--     'duplicate-doc-alias',
+--     'undefined-doc-class',
+--     'undefined-doc-name',
+--     'circle-doc-class',
+--     'undefined-doc-param',
+--     'duplicate-doc-param',
+--     'doc-field-no-class',
+--     'duplicate-doc-field',
+--     'unknown-diag-code',
+--     'unknown-cast-variable',
+--     'unknown-operator',
+-- } {
+--     group    = 'luadoc',
+--     severity = 'Warning',
+--     status   = 'Any',
+-- }
 
-m.register {
-    'newline-call',
-    'newfield-call',
-    'ambiguity-1',
-    'count-down-loop',
-    'different-requires',
-} {
-    group    = 'ambiguity',
-    severity = 'Warning',
-    status   = 'Any',
-}
+-- m.register {
+--     'codestyle-check'
+-- } {
+--     group    = 'codestyle',
+--     severity = 'Warning',
+--     status   = 'None',
+-- }
 
-m.register {
-    'await-in-sync',
-    'not-yieldable',
-} {
-    group    = 'await',
-    severity = 'Warning',
-    status   = 'None',
-}
+-- m.register {
+--     'spell-check'
+-- } {
+--     group    = 'codestyle',
+--     severity = 'Information',
+--     status   = 'None',
+-- }
 
-m.register {
-    'no-unknown',
-} {
-    group    = 'strong',
-    severity = 'Warning',
-    status   = 'None',
-}
+-- m.register {
+--     'newline-call',
+--     'newfield-call',
+--     'ambiguity-1',
+--     'count-down-loop',
+--     'different-requires',
+-- } {
+--     group    = 'ambiguity',
+--     severity = 'Warning',
+--     status   = 'Any',
+-- }
 
-m.register {
-    'redefined-local',
-} {
-    group    = 'redefined',
-    severity = 'Hint',
-    status   = 'Opened',
-}
+-- m.register {
+--     'await-in-sync',
+--     'not-yieldable',
+-- } {
+--     group    = 'await',
+--     severity = 'Warning',
+--     status   = 'None',
+-- }
 
-m.register {
-    'undefined-global',
-    'global-in-nil-env',
-} {
-    group    = 'global',
-    severity = 'Warning',
-    status   = 'Any',
-}
+-- m.register {
+--     'no-unknown',
+-- } {
+--     group    = 'strong',
+--     severity = 'Warning',
+--     status   = 'None',
+-- }
 
-m.register {
-    'lowercase-global',
-    'undefined-env-child',
-} {
-    group    = 'global',
-    severity = 'Information',
-    status   = 'Any',
-}
+-- m.register {
+--     'redefined-local',
+-- } {
+--     group    = 'redefined',
+--     severity = 'Hint',
+--     status   = 'Opened',
+-- }
 
-m.register {
-    'duplicate-index',
-} {
-    group    = 'duplicate',
-    severity = 'Warning',
-    status   = 'Any',
-}
+-- m.register {
+--     'undefined-global',
+--     'global-in-nil-env',
+-- } {
+--     group    = 'global',
+--     severity = 'Warning',
+--     status   = 'Any',
+-- }
 
-m.register {
-    'duplicate-set-field',
-} {
-    group    = 'duplicate',
-    severity = 'Warning',
-    status   = 'Opened',
-}
+-- m.register {
+--     'lowercase-global',
+--     'undefined-env-child',
+-- } {
+--     group    = 'global',
+--     severity = 'Information',
+--     status   = 'Any',
+-- }
 
-m.register {
-    'close-non-object',
-    'deprecated',
-    'discard-returns',
-    'invisible',
-} {
-    group    = 'strict',
-    severity = 'Warning',
-    status   = 'Any',
-}
+-- m.register {
+--     'duplicate-index',
+-- } {
+--     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',
+--     status   = 'Any',
+-- }
 
 ---@return table<string, DiagnosticSeverity>
 function m.getDefaultSeverity()

From 66a31eb773948054c2dc6cd66f7b4eb9f30d51fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Ritzl?= <bjorn.ritzl@gmail.com>
Date: Wed, 11 Jan 2023 16:49:49 +0100
Subject: [PATCH 02/48] Added luafilesystem (#2)

* Added luafilesystem

* Update make.lua

* Update make.lua

* Update modules.cpp

* Update modules.cpp

* Load luacheckrc config

* Update luacheck.lua

* Update luacheck.lua

* Update code_format.lua

* Update code_format.lua

* Update code_format.lua

* Update code_format.lua

* Update build.yml

* Update make.lua

* Update make.lua

* Update make.lua

* Update make.lua

* Update make.lua
---
 .github/workflows/build.yml          |  6 +++++
 .gitmodules                          |  3 +++
 3rd/luafilesystem                    |  1 +
 make.lua                             | 14 +++++++++++
 make/modules.cpp                     |  3 +++
 script/core/diagnostics/luacheck.lua | 37 +++++++++++++++++++++++++++-
 6 files changed, 63 insertions(+), 1 deletion(-)
 create mode 160000 3rd/luafilesystem

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c7412228a..45d1c095a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -34,6 +34,12 @@ jobs:
           sudo apt-get update
           sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
 
+      - name: Install linux-x86 dependencies
+        if: ${{ matrix.platform == 'linux-x86' }}
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y glibc-source
+
       - uses: actions/checkout@v2
         with:
           submodules: recursive
diff --git a/.gitmodules b/.gitmodules
index 1dfe374c8..ef37cab50 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -16,3 +16,6 @@
 [submodule "3rd/EmmyLuaCodeStyle"]
 	path = 3rd/EmmyLuaCodeStyle
 	url = https://github.com/CppCXY/EmmyLuaCodeStyle
+[submodule "3rd/luafilesystem"]
+	path = 3rd/luafilesystem
+	url = https://github.com/lunarmodules/luafilesystem
diff --git a/3rd/luafilesystem b/3rd/luafilesystem
new file mode 160000
index 000000000..7c6e1b013
--- /dev/null
+++ b/3rd/luafilesystem
@@ -0,0 +1 @@
+Subproject commit 7c6e1b013caec0602ca4796df3b1d7253a2dd258
diff --git a/make.lua b/make.lua
index 3caca4f50..5a65e1e7d 100644
--- a/make.lua
+++ b/make.lua
@@ -24,9 +24,23 @@ lm:source_set 'lpeglabel' {
     },
 }
 
+lm:source_set 'luafilesystem' {
+    rootdir = '3rd',
+    includes = "bee.lua/3rd/lua",
+    sources = "luafilesystem/src/*.c",
+    linux = {
+        flags = "-g -Wall -Werror=format",
+        defines = {
+            "__STDC_LIMIT_MACROS",
+            "_BSD_SOURCE"
+        }
+    }
+}
+
 lm:executable "lua-language-server" {
     deps = {
         "lpeglabel",
+        "luafilesystem",
         "source_bootstrap",
         includeCodeFormat and "code_format" or nil,
     },
diff --git a/make/modules.cpp b/make/modules.cpp
index 802e69161..c4f06fcb2 100644
--- a/make/modules.cpp
+++ b/make/modules.cpp
@@ -3,6 +3,9 @@
 extern "C" int luaopen_lpeglabel (lua_State *L);
 static ::bee::lua::callfunc _init(::bee::lua::register_module, "lpeglabel", luaopen_lpeglabel);
 
+extern "C" int luaopen_lfs (lua_State *L);
+static ::bee::lua::callfunc _init_lfs(::bee::lua::register_module, "lfs", luaopen_lfs);
+
 #ifdef CODE_FORMAT
 extern "C" int luaopen_code_format(lua_State *L);
 static ::bee::lua::callfunc _init_code_format(::bee::lua::register_module, "code_format",
diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index a1d7d2a13..aa76f63d3 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -7,6 +7,10 @@ local luacheck = require "luacheck.check"
 local lcstages = require "luacheck.stages"
 local lcformat = require "luacheck.format"
 local lcfilter = require "luacheck.filter"
+local lcconfig = require "luacheck.config"
+local workspace = require "workspace"
+local util     = require 'utility'
+local fs = require "luacheck.fs"
 
 local function pprint(t)
     if type(t) ~= "table" then
@@ -18,10 +22,41 @@ local function pprint(t)
     end
 end
 
+local config_stack = nil
+
+-- a bit hacky but this will monkey patch the isLua function so that
+-- we can know when the .luacheckrc file has been changed
+-- a call to isLua is done in workspace.lua fw.event() on change
+-- we could also add .luacheck as a recognised file association using
+-- a config file (see config.template and the files.associations value)
+-- but this works equally well for our purposes
+local isLua = files.isLua
+files.isLua = function(uri)
+    if util.stringEndWith(uri:lower(), '.luacheckrc') then
+        config_stack = nil
+        return false
+    end
+    return isLua(uri)
+end
+
 return function (uri, callback)
+
+    if not config_stack then
+        log.info("loading .luacheckrc")
+        local rootUri = (workspace.rootUri or ""):gsub("file://", "")
+        local path = fs.join(rootUri, ".luacheckrc")
+        local global_path = nil
+        local config = lcconfig.load_config(path, global_path)
+        config_stack = lcconfig.stack_configs({ config })
+    end
+
     local text = files.getText(uri)
     local report = luacheck(text)
-    lcfilter.filter({report})
+    local reports = { report }
+    local opts = { config_stack:get_options(uri) }
+    local stds = config_stack:get_stds()
+    lcfilter.filter(reports, opts, stds)
+
     for i,warning in ipairs(report.filtered_warnings) do
         local message = lcformat.get_message(warning)
         local start = ((warning.line - 1) * 10000) + warning.column - 1

From c2c26632eeec643e689cc5f438e018aecf99ea62 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 13:45:00 +0100
Subject: [PATCH 03/48] WIP

---
 .github/workflows/build.yml                   | 28 +++++++++----------
 defold/game.project                           |  3 ++
 defold/lsp-lua-language-server/ext.manifest   |  1 +
 .../lsp-lua-language-server/lsp.editor_script | 20 +++++++++++++
 .../plugins/share/config.json                 | 16 +++++++++++
 5 files changed, 54 insertions(+), 14 deletions(-)
 create mode 100644 defold/game.project
 create mode 100644 defold/lsp-lua-language-server/ext.manifest
 create mode 100644 defold/lsp-lua-language-server/lsp.editor_script
 create mode 100644 defold/lsp-lua-language-server/plugins/share/config.json

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 45d1c095a..be3bb18d3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,24 +21,24 @@ jobs:
       matrix:
         include:
           - { os: ubuntu-18.04,   target: linux,   platform: linux-x64    }
-          - { os: ubuntu-18.04,   target: linux,   platform: linux-arm64  }
+          # - { os: ubuntu-18.04,   target: linux,   platform: linux-arm64  }
           - { os: macos-11,       target: darwin,  platform: darwin-x64   }
-          - { os: macos-11,       target: darwin,  platform: darwin-arm64 }
-          - { os: windows-latest, target: windows, platform: win32-ia32   }
+          # - { os: macos-11,       target: darwin,  platform: darwin-arm64 }
+          # - { os: windows-latest, target: windows, platform: win32-ia32   }
           - { os: windows-latest, target: windows, platform: win32-x64    }
     runs-on: ${{ matrix.os }}
     steps:
-      - name: Install aarch64-linux-gnu
-        if: ${{ matrix.platform == 'linux-arm64' }}
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
-
-      - name: Install linux-x86 dependencies
-        if: ${{ matrix.platform == 'linux-x86' }}
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y glibc-source
+      # - name: Install aarch64-linux-gnu
+      #   if: ${{ matrix.platform == 'linux-arm64' }}
+      #   run: |
+      #     sudo apt-get update
+      #     sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
+
+      # - name: Install linux-x86 dependencies
+      #   if: ${{ matrix.platform == 'linux-x86' }}
+      #   run: |
+      #     sudo apt-get update
+      #     sudo apt-get install -y glibc-source
 
       - uses: actions/checkout@v2
         with:
diff --git a/defold/game.project b/defold/game.project
new file mode 100644
index 000000000..5e92e9641
--- /dev/null
+++ b/defold/game.project
@@ -0,0 +1,3 @@
+[library]
+include_dirs = lsp-lua-language-server
+
diff --git a/defold/lsp-lua-language-server/ext.manifest b/defold/lsp-lua-language-server/ext.manifest
new file mode 100644
index 000000000..5dbd6791c
--- /dev/null
+++ b/defold/lsp-lua-language-server/ext.manifest
@@ -0,0 +1 @@
+name: "lsp-lua-language-server"
\ No newline at end of file
diff --git a/defold/lsp-lua-language-server/lsp.editor_script b/defold/lsp-lua-language-server/lsp.editor_script
new file mode 100644
index 000000000..ccdc04dff
--- /dev/null
+++ b/defold/lsp-lua-language-server/lsp.editor_script
@@ -0,0 +1,20 @@
+local M = {}
+
+function M.get_language_servers()
+    local cmd = './build/plugins/lsp-lua-language-server/plugins/bin/' .. editor.platform .. '/bin/lua-language-server'
+    if editor.platform == 'x86_64-win32' then
+        cmd = cmd .. '.exe'
+    end
+    local config_arg = '--configpath=build/plugins/lsp-lua-language-server/plugins/share/config.json'
+    return {
+        {
+            languages = {'lua'},
+            watched_files = {
+                { pattern = '**/.luacheckrc' }
+            },
+            command = { cmd, config_arg }
+        }
+    }
+end
+
+return M
diff --git a/defold/lsp-lua-language-server/plugins/share/config.json b/defold/lsp-lua-language-server/plugins/share/config.json
new file mode 100644
index 000000000..975b8f294
--- /dev/null
+++ b/defold/lsp-lua-language-server/plugins/share/config.json
@@ -0,0 +1,16 @@
+{
+    "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
+    "diagnostics": {
+        "workspaceEvent": "None",
+        "enable": true
+    },
+    "Lua": {
+        "runtime": {
+            "version": "Lua 5.1"
+        }
+    },
+    "window": {
+        "progressBar": false,
+        "statusBar": false
+    }
+}
\ No newline at end of file

From 6c1b96c53f9eab9b55e0a1ce1a93cd1c97837e24 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 14:05:23 +0100
Subject: [PATCH 04/48] wip

---
 .github/workflows/build.yml | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index be3bb18d3..f53b882c4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -20,12 +20,12 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-18.04,   target: linux,   platform: linux-x64    }
+          - { os: ubuntu-18.04,   target: linux,   platform: linux-x64, zip: x86_64-linux    }
           # - { os: ubuntu-18.04,   target: linux,   platform: linux-arm64  }
-          - { os: macos-11,       target: darwin,  platform: darwin-x64   }
+          - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
           # - { os: macos-11,       target: darwin,  platform: darwin-arm64 }
           # - { os: windows-latest, target: windows, platform: win32-ia32   }
-          - { os: windows-latest, target: windows, platform: win32-x64    }
+          - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }
     runs-on: ${{ matrix.os }}
     steps:
       # - name: Install aarch64-linux-gnu
@@ -65,7 +65,7 @@ jobs:
           fi
 
           # Package name w/ version
-          PKG_BASENAME="${{ env.PROJECT }}-${PKG_VERSION}-${{ matrix.platform }}"
+          PKG_BASENAME="${{ matrix.zip }}"
 
           # Full name of the tarball asset
           PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}"
@@ -136,3 +136,18 @@ jobs:
           files: |
             ${{ steps.vars.outputs.PKG_PATH }}
             ${{ steps.submodules.outputs.PKG_SUBMOD_PATH }}
+  create-extension:
+    needs: compile
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/download-artifact@v3
+        with:
+          name: |
+            x86_64-linux
+            x86_64-macos
+            x86_64-win32
+      - name: "Create "
+        shell: bash
+        run: |
+          ls

From 3fec5c9e633c1bd73c7c51f82be341348cba3a39 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 14:16:10 +0100
Subject: [PATCH 05/48] wip

---
 .github/workflows/build.yml | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f53b882c4..b0dff0e3e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -142,11 +142,6 @@ jobs:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/download-artifact@v3
-        with:
-          name: |
-            x86_64-linux
-            x86_64-macos
-            x86_64-win32
       - name: "Create "
         shell: bash
         run: |

From 498889098a5091c2ddbb9b00258a5a34fe1d49cf Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 14:50:12 +0100
Subject: [PATCH 06/48] wip

---
 .github/workflows/build.yml | 94 +++++--------------------------------
 1 file changed, 12 insertions(+), 82 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b0dff0e3e..07ecd91fe 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,25 +21,10 @@ jobs:
       matrix:
         include:
           - { os: ubuntu-18.04,   target: linux,   platform: linux-x64, zip: x86_64-linux    }
-          # - { os: ubuntu-18.04,   target: linux,   platform: linux-arm64  }
           - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
-          # - { os: macos-11,       target: darwin,  platform: darwin-arm64 }
-          # - { os: windows-latest, target: windows, platform: win32-ia32   }
           - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }
     runs-on: ${{ matrix.os }}
     steps:
-      # - name: Install aarch64-linux-gnu
-      #   if: ${{ matrix.platform == 'linux-arm64' }}
-      #   run: |
-      #     sudo apt-get update
-      #     sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
-
-      # - name: Install linux-x86 dependencies
-      #   if: ${{ matrix.platform == 'linux-x86' }}
-      #   run: |
-      #     sudo apt-get update
-      #     sudo apt-get install -y glibc-source
-
       - uses: actions/checkout@v2
         with:
           submodules: recursive
@@ -57,85 +42,30 @@ jobs:
             PKG_VERSION=${GITHUB_SHA:0:7}
           fi
 
-          # Package suffix relative to the platform
-          if [[ "${{ matrix.target }}" = windows ]]; then
-            PKG_SUFFIX="zip"
-          else
-            PKG_SUFFIX="tar.gz"
-          fi
-
           # Package name w/ version
           PKG_BASENAME="${{ matrix.zip }}"
 
           # Full name of the tarball asset
-          PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}"
+          PKG_NAME="${PKG_BASENAME}.zip"
 
-          # Staging area for tarballs
-          PKG_STAGING="ci_staging/$PKG_BASENAME"
+          zip -r ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
 
-          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
         with:
           name: ${{ steps.vars.outputs.PKG_BASENAME }}
           path: |
-            ${{ env.BIN_DIR }}
-            main.lua
-            debugger.lua
-            LICENSE
-            changelog.md
-            locale
-            doc
-            meta
-            script
-
-      - name: Package tarballs
-        if: startsWith(github.ref, 'refs/tags/')
-        shell: bash
-        run: |
-          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
-          NAME=${{ steps.vars.outputs.PKG_NAME }}
-
-          # Making the staging area
-          mkdir -p ${STAGING}
-
-          # Copying binary and runtime files to staging area
-          cp -r main.lua debugger.lua LICENSE changelog.md locale meta script ${{ env.BIN_DIR }} ${STAGING}
-
-          # Creating release assets
-          pushd "${STAGING}/" >/dev/null
-          if [[ "${{ matrix.target }}" = windows ]]; then
-            7z -y a ${NAME} * | tail -2
-          else
-            tar czf ${NAME} *
-          fi
-          popd >/dev/null
-
-      # Packaging submodules for homebrew distribution
-      - name: Package submodules
-        id: submodules
-        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.platform == 'darwin-x64' }}
-        run: |
-          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
-          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}*"
-
-          echo ::set-output name=PKG_SUBMOD_PATH::${PKG_SUBMOD_PATH}
-
-      - name: Publish release assets
-        uses: softprops/action-gh-release@v1
-        if: startsWith(github.ref, 'refs/tags/')
-        with:
-          generate_release_notes: true
-          files: |
-            ${{ steps.vars.outputs.PKG_PATH }}
-            ${{ steps.submodules.outputs.PKG_SUBMOD_PATH }}
+            ${{ steps.vars.outputs.PKG_NAME }}
+
+      # - name: Publish release assets
+      #   uses: softprops/action-gh-release@v1
+      #   if: startsWith(github.ref, 'refs/tags/')
+      #   with:
+      #     generate_release_notes: true
+      #     files: | # fix PKG_PATH!
+      #       ${{ steps.vars.outputs.PKG_PATH }} 
   create-extension:
     needs: compile
     runs-on: ubuntu-latest
@@ -145,4 +75,4 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
-          ls
+          ls -l

From 0561c8c2dc5cc40cb4eb74a06f5c71ac634c9b22 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:03:27 +0100
Subject: [PATCH 07/48] wip

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 07ecd91fe..24221ad85 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -48,7 +48,7 @@ jobs:
           # Full name of the tarball asset
           PKG_NAME="${PKG_BASENAME}.zip"
 
-          zip -r ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
+          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
 
           echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
           echo ::set-output name=PKG_NAME::${PKG_NAME}

From 4cba1a3f121e4747b87dba1d2c98c31f913dc9ba Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:16:14 +0100
Subject: [PATCH 08/48] wip

---
 .github/workflows/build.yml | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 24221ad85..5ae357f05 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -31,7 +31,7 @@ jobs:
       - uses: actboy168/setup-luamake@master
       - run: luamake -platform ${{ matrix.platform }}
 
-      - name: Setting up workflow variables
+      - name: Create bin archive
         id: vars
         shell: bash
         run: |
@@ -75,4 +75,15 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
-          ls -l
+          cp x86_64-linux/x86_64-linux.zip defold/lsp-lua-language-server/plugins/bin
+          cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
+          cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
+          ls -l defold
+          ls -l defold/lsp-lua-language-server/plugins/bin
+          7z -y a archive.zip defold/*
+      - name: Publish release assets
+        uses: softprops/action-gh-release@v1
+        # if: startsWith(github.ref, 'refs/tags/')
+        with:
+          generate_release_notes: true
+          files: archive.zip

From 3984776d42fae86cc2b372184213b666eca9b4a1 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:22:25 +0100
Subject: [PATCH 09/48] wip

---
 .github/workflows/build.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5ae357f05..51135b9eb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -75,6 +75,8 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
+          ls -l 
+          ls -l x86_64-linux
           cp x86_64-linux/x86_64-linux.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
@@ -83,7 +85,7 @@ jobs:
           7z -y a archive.zip defold/*
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
-        # if: startsWith(github.ref, 'refs/tags/')
+        if: startsWith(github.ref, 'refs/tags/')
         with:
           generate_release_notes: true
           files: archive.zip

From c608b3fbe62bfe0b42a570fa7e5aed24db1d38db Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:30:04 +0100
Subject: [PATCH 10/48] wip

---
 .github/workflows/build.yml | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 51135b9eb..30e2a3fcf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -75,14 +75,15 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
-          ls -l 
-          ls -l x86_64-linux
+          mkdir defold/lsp-lua-language-server/plugins/bin
           cp x86_64-linux/x86_64-linux.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
-          ls -l defold
-          ls -l defold/lsp-lua-language-server/plugins/bin
           7z -y a archive.zip defold/*
+      - uses: actions/upload-artifact@v2
+        with:
+          name: result
+          path: archive.zip
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
         if: startsWith(github.ref, 'refs/tags/')

From f5a48b9d6eb8e3bc2dfff5307b0adddeeb4a2d84 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:36:33 +0100
Subject: [PATCH 11/48] wip

---
 .github/workflows/build.yml | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 30e2a3fcf..57baf320a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -58,14 +58,6 @@ jobs:
           name: ${{ steps.vars.outputs.PKG_BASENAME }}
           path: |
             ${{ steps.vars.outputs.PKG_NAME }}
-
-      # - name: Publish release assets
-      #   uses: softprops/action-gh-release@v1
-      #   if: startsWith(github.ref, 'refs/tags/')
-      #   with:
-      #     generate_release_notes: true
-      #     files: | # fix PKG_PATH!
-      #       ${{ steps.vars.outputs.PKG_PATH }} 
   create-extension:
     needs: compile
     runs-on: ubuntu-latest
@@ -79,7 +71,8 @@ jobs:
           cp x86_64-linux/x86_64-linux.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
-          7z -y a archive.zip defold/*
+          cd defold
+          7z -y a archive.zip *
       - uses: actions/upload-artifact@v2
         with:
           name: result

From 30dff5af4225cb12253c1f66daa78e05cc86786f Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 12 Jan 2023 15:41:35 +0100
Subject: [PATCH 12/48] wip

---
 .github/workflows/build.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 57baf320a..1a997a1b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -75,11 +75,11 @@ jobs:
           7z -y a archive.zip *
       - uses: actions/upload-artifact@v2
         with:
-          name: result
-          path: archive.zip
+          name: result.zip
+          path: defold/archive.zip
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
         if: startsWith(github.ref, 'refs/tags/')
         with:
           generate_release_notes: true
-          files: archive.zip
+          files: defold/archive.zip

From 8b01ff0cd7a4c92ddf8a0983242f90516dd578b7 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 13 Jan 2023 12:35:31 +0100
Subject: [PATCH 13/48] rename release artifact

---
 .github/workflows/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1a997a1b3..e5d60b358 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -72,14 +72,14 @@ jobs:
           cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
           cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
           cd defold
-          7z -y a archive.zip *
+          7z -y a release.zip *
       - uses: actions/upload-artifact@v2
         with:
-          name: result.zip
-          path: defold/archive.zip
+          name: release
+          path: defold/release.zip
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
         if: startsWith(github.ref, 'refs/tags/')
         with:
           generate_release_notes: true
-          files: defold/archive.zip
+          files: defold/release.zip

From bb50f47218a0c3c29933d4c9750ce96830255ca6 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 16 Jan 2023 13:20:54 +0100
Subject: [PATCH 14/48] Change archive building

---
 .github/workflows/build.yml | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e5d60b358..0055a32da 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -67,11 +67,29 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
-          mkdir defold/lsp-lua-language-server/plugins/bin
-          cp x86_64-linux/x86_64-linux.zip defold/lsp-lua-language-server/plugins/bin
-          cp x86_64-macos/x86_64-macos.zip defold/lsp-lua-language-server/plugins/bin
-          cp x86_64-win32/x86_64-win32.zip defold/lsp-lua-language-server/plugins/bin
           cd defold
+
+          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-linux
+          7z -y x ../x86_64-linux/x86_64-linux.zip lsp-lua-language-server/plugins/bin/x86_64-linux
+          cd lsp-lua-language-server/plugins
+          7z -y a x86_64-linux.zip bin
+          rm -r bin 
+          cd ../..
+
+          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-macos
+          7z -y x ../x86_64-macos/x86_64-macos.zip lsp-lua-language-server/plugins/bin/x86_64-macos
+          cd lsp-lua-language-server/plugins
+          7z -y a x86_64-macos.zip bin
+          rm -r bin 
+          cd ../..
+
+          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-win32
+          7z -y x ../x86_64-win32/x86_64-win32.zip lsp-lua-language-server/plugins/bin/x86_64-win32
+          cd lsp-lua-language-server/plugins
+          7z -y a x86_64-win32.zip bin
+          rm -r bin 
+          cd ../..
+
           7z -y a release.zip *
       - uses: actions/upload-artifact@v2
         with:

From 850395f2d22966a087d11e7c5acac16e101c19eb Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 16 Jan 2023 13:31:20 +0100
Subject: [PATCH 15/48] Change archive building

---
 .github/workflows/build.yml | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0055a32da..3d9455bb1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -67,28 +67,33 @@ jobs:
       - name: "Create "
         shell: bash
         run: |
-          cd defold
 
-          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-linux
-          7z -y x ../x86_64-linux/x86_64-linux.zip lsp-lua-language-server/plugins/bin/x86_64-linux
-          cd lsp-lua-language-server/plugins
+          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-linux
+          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-linux
+          7z -y x ~/x86_64-linux/x86_64-linux.zip
+          popd
+          pushd defold/lsp-lua-language-server/plugins/
           7z -y a x86_64-linux.zip bin
           rm -r bin 
-          cd ../..
+          popd
 
-          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-macos
-          7z -y x ../x86_64-macos/x86_64-macos.zip lsp-lua-language-server/plugins/bin/x86_64-macos
-          cd lsp-lua-language-server/plugins
+          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-macos
+          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-macos
+          7z -y x ~/x86_64-macos/x86_64-macos.zip
+          popd
+          pushd defold/lsp-lua-language-server/plugins/
           7z -y a x86_64-macos.zip bin
           rm -r bin 
-          cd ../..
+          popd
 
-          mkdir -p lsp-lua-language-server/plugins/bin/x86_64-win32
-          7z -y x ../x86_64-win32/x86_64-win32.zip lsp-lua-language-server/plugins/bin/x86_64-win32
-          cd lsp-lua-language-server/plugins
+          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-win32
+          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-win32
+          7z -y x ~/x86_64-win32/x86_64-win32.zip
+          popd
+          pushd defold/lsp-lua-language-server/plugins/
           7z -y a x86_64-win32.zip bin
           rm -r bin 
-          cd ../..
+          popd
 
           7z -y a release.zip *
       - uses: actions/upload-artifact@v2

From b4d36e0d475b82c3b760d02c0bc1b98119c8f018 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 16 Jan 2023 13:46:13 +0100
Subject: [PATCH 16/48] Change archive building

---
 .github/workflows/build.yml | 34 +++++++++++++---------------------
 1 file changed, 13 insertions(+), 21 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3d9455bb1..540b16dcd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,33 +68,25 @@ jobs:
         shell: bash
         run: |
 
-          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-linux
-          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-linux
-          7z -y x ~/x86_64-linux/x86_64-linux.zip
-          popd
-          pushd defold/lsp-lua-language-server/plugins/
-          7z -y a x86_64-linux.zip bin
-          rm -r bin 
-          popd
-
-          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-macos
-          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-macos
-          7z -y x ~/x86_64-macos/x86_64-macos.zip
-          popd
-          pushd defold/lsp-lua-language-server/plugins/
+          7z -y x x86_64-macos/x86_64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-macos
+          cd defold/lsp-lua-language-server/plugins
           7z -y a x86_64-macos.zip bin
           rm -r bin 
-          popd
+          cd -
 
-          mkdir -p defold/lsp-lua-language-server/plugins/bin/x86_64-win32
-          pushd defold/lsp-lua-language-server/plugins/bin/x86_64-win32
-          7z -y x ~/x86_64-win32/x86_64-win32.zip
-          popd
-          pushd defold/lsp-lua-language-server/plugins/
+          7z -y x x86_64-win32/x86_64-win32.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-win32
+          cd defold/lsp-lua-language-server/plugins
           7z -y a x86_64-win32.zip bin
           rm -r bin 
-          popd
+          cd -
+
+          7z -y x x86_64-linux/x86_64-linux.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-linux
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a x86_64-linux.zip bin
+          rm -r bin 
+          cd -
 
+          cd defold
           7z -y a release.zip *
       - uses: actions/upload-artifact@v2
         with:

From 012575a2fcf30a754d974be3d83c09a2850685e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Ritzl?= <bjorn.ritzl@gmail.com>
Date: Mon, 16 Jan 2023 23:58:57 +0100
Subject: [PATCH 17/48] Add Defold globals

---
 script/core/diagnostics/luacheck.lua | 82 +++++++++++++++++++++++-----
 1 file changed, 68 insertions(+), 14 deletions(-)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index aa76f63d3..30a1732c4 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -1,26 +1,16 @@
 local files   = require 'files'
-local guide   = require 'parser.guide'
-local lang    = require 'language'
 local define  = require 'proto.define'
 local log     = require 'log'
+local lspconfig = require 'config'
 local luacheck = require "luacheck.check"
-local lcstages = require "luacheck.stages"
 local lcformat = require "luacheck.format"
 local lcfilter = require "luacheck.filter"
 local lcconfig = require "luacheck.config"
 local workspace = require "workspace"
 local util     = require 'utility'
-local fs = require "luacheck.fs"
+local fs       = require "luacheck.fs"
+local inspect    = require 'inspect'
 
-local function pprint(t)
-    if type(t) ~= "table" then
-        log.info(t)
-        return
-    end
-    for k,v in pairs(t) do
-        log.info("t k:", k, "v:", v)
-    end
-end
 
 local config_stack = nil
 
@@ -39,6 +29,59 @@ files.isLua = function(uri)
     return isLua(uri)
 end
 
+local defold_config = {
+    options = {
+        stds = {},
+        files = {},
+        globals = {
+            -- defold
+            "buffer",
+            "collectionproxy",
+            "collectionfactory",
+            "crash",
+            "factory",
+            "go",
+            "gui",
+            "html5",
+            "http",
+            "image",
+            "json",
+            "label",
+            "model",
+            "msg",
+            "particlefx",
+            "physics",
+            "profiler",
+            "render",
+            "resource",
+            "socket",
+            "sprite",
+            "sound",
+            "sys",
+            "timer",
+            "tilemap",
+            "url",
+            "vmath",
+            "window",
+            "zlib",
+
+            -- defold builtin functions
+            "pprint",
+            "hash",
+            "hash_to_hex",
+
+            -- script lifecycle functions
+            "init",
+            "final",
+            "update",
+            "fixed_update",
+            "on_input",
+            "on_message",
+            "on_reload",
+        }
+    }
+}
+
 return function (uri, callback)
 
     if not config_stack then
@@ -47,7 +90,18 @@ return function (uri, callback)
         local path = fs.join(rootUri, ".luacheckrc")
         local global_path = nil
         local config = lcconfig.load_config(path, global_path)
-        config_stack = lcconfig.stack_configs({ config })
+
+        -- get editor provided globals
+        local editor_config = {
+            options = {
+                stds = {},
+                files = {},
+                globals = lspconfig.get(workspace.rootUri, 'Lua.diagnostics.globals') or {},
+            }
+        }
+
+        -- create final config stack combining LuaCheck config, Defold globals and editor globals
+        config_stack = lcconfig.stack_configs({ config, defold_config, editor_config })
     end
 
     local text = files.getText(uri)

From ec7ed7a4165dcef45a10295ce798d80a3af6e167 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Tue, 17 Jan 2023 11:13:02 +0100
Subject: [PATCH 18/48] Remove defold_config, add default .luacheckrc config

---
 script/core/diagnostics/luacheck.lua | 57 +---------------------------
 1 file changed, 2 insertions(+), 55 deletions(-)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index 30a1732c4..42c03f5cd 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -29,59 +29,6 @@ files.isLua = function(uri)
     return isLua(uri)
 end
 
-local defold_config = {
-    options = {
-        stds = {},
-        files = {},
-        globals = {
-            -- defold
-            "buffer",
-            "collectionproxy",
-            "collectionfactory",
-            "crash",
-            "factory",
-            "go",
-            "gui",
-            "html5",
-            "http",
-            "image",
-            "json",
-            "label",
-            "model",
-            "msg",
-            "particlefx",
-            "physics",
-            "profiler",
-            "render",
-            "resource",
-            "socket",
-            "sprite",
-            "sound",
-            "sys",
-            "timer",
-            "tilemap",
-            "url",
-            "vmath",
-            "window",
-            "zlib",
-
-            -- defold builtin functions
-            "pprint",
-            "hash",
-            "hash_to_hex",
-
-            -- script lifecycle functions
-            "init",
-            "final",
-            "update",
-            "fixed_update",
-            "on_input",
-            "on_message",
-            "on_reload",
-        }
-    }
-}
-
 return function (uri, callback)
 
     if not config_stack then
@@ -89,7 +36,7 @@ return function (uri, callback)
         local rootUri = (workspace.rootUri or ""):gsub("file://", "")
         local path = fs.join(rootUri, ".luacheckrc")
         local global_path = nil
-        local config = lcconfig.load_config(path, global_path)
+        local config = lcconfig.load_config(path, global_path) or {options={stds={},files={},globals={}}}
 
         -- get editor provided globals
         local editor_config = {
@@ -101,7 +48,7 @@ return function (uri, callback)
         }
 
         -- create final config stack combining LuaCheck config, Defold globals and editor globals
-        config_stack = lcconfig.stack_configs({ config, defold_config, editor_config })
+        config_stack = lcconfig.stack_configs({ config, editor_config })
     end
 
     local text = files.getText(uri)

From ba0ba9828e63f28026a418fb650be590f465f7bb Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 13 Feb 2023 14:57:51 +0100
Subject: [PATCH 19/48] Attempt to add workspace diagnostics

---
 script/provider/diagnostic.lua |  5 -----
 script/provider/provider.lua   | 10 +++++-----
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua
index 90c5a81ce..fb42e84c0 100644
--- a/script/provider/diagnostic.lua
+++ b/script/provider/diagnostic.lua
@@ -634,11 +634,6 @@ function m.pullDiagnosticScope(callback)
             end, id)
         end
     end
-
-    -- sleep for ever
-    while true do
-        await.sleep(1.0)
-    end
 end
 
 function m.refreshClient()
diff --git a/script/provider/provider.lua b/script/provider/provider.lua
index 319d00a9b..abdf888a3 100644
--- a/script/provider/provider.lua
+++ b/script/provider/provider.lua
@@ -1437,11 +1437,11 @@ m.register 'textDocument/diagnostic' {
 
 m.register 'workspace/diagnostic' {
     --preview = true,
-    --capability = {
-    --    diagnosticProvider = {
-    --        workspaceDiagnostics  = false,
-    --    }
-    --},
+    capability = {
+       diagnosticProvider = {
+           workspaceDiagnostics = true,
+       }
+    },
     ---@async
     function (params)
         local core = require 'provider.diagnostic'

From c7c82acb028d7dcda25e5817e6ff2d43415127cf Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 13 Feb 2023 14:57:51 +0100
Subject: [PATCH 20/48] Add diagnostics

---
 .../lsp-lua-language-server/lsp.editor_script |  2 +-
 script/provider/diagnostic.lua                | 17 ++---
 script/provider/provider.lua                  | 65 +++++++++----------
 3 files changed, 41 insertions(+), 43 deletions(-)

diff --git a/defold/lsp-lua-language-server/lsp.editor_script b/defold/lsp-lua-language-server/lsp.editor_script
index ccdc04dff..d1f7c27db 100644
--- a/defold/lsp-lua-language-server/lsp.editor_script
+++ b/defold/lsp-lua-language-server/lsp.editor_script
@@ -1,7 +1,7 @@
 local M = {}
 
 function M.get_language_servers()
-    local cmd = './build/plugins/lsp-lua-language-server/plugins/bin/' .. editor.platform .. '/bin/lua-language-server'
+    local cmd = 'build/plugins/lsp-lua-language-server/plugins/bin/' .. editor.platform .. '/bin/lua-language-server'
     if editor.platform == 'x86_64-win32' then
         cmd = cmd .. '.exe'
     end
diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua
index fb42e84c0..41e100101 100644
--- a/script/provider/diagnostic.lua
+++ b/script/provider/diagnostic.lua
@@ -606,19 +606,19 @@ function m.pullDiagnosticScope(callback)
         and config.get(scp.uri, 'Lua.diagnostics.enable') then
             local id = 'diagnosticsScope:' .. scp:getName()
             await.close(id)
-            await.call(function () ---@async
+            await.await(function () ---@async
                 processing = processing + 1
                 local _ <close> = util.defer(function ()
                     processing = processing - 1
                 end)
 
-                local delay = config.get(scp.uri, 'Lua.diagnostics.workspaceDelay') / 1000
-                if delay < 0 then
-                    return
-                end
-                print(delay)
-                await.sleep(math.max(delay, 0.2))
-                print('start')
+                -- local delay = config.get(scp.uri, 'Lua.diagnostics.workspaceDelay') / 1000
+                -- if delay < 0 then
+                --     return
+                -- end
+                -- print(delay)
+                -- await.sleep(math.max(delay, 0.2))
+                -- print('start')
 
                 m.awaitDiagnosticsScope(scp.uri, function (fileUri)
                     local suc, result, unchanged = xpcall(m.pullDiagnostic, log.error, fileUri, true)
@@ -634,6 +634,7 @@ function m.pullDiagnosticScope(callback)
             end, id)
         end
     end
+    await.delay()
 end
 
 function m.refreshClient()
diff --git a/script/provider/provider.lua b/script/provider/provider.lua
index abdf888a3..e61d6a0d1 100644
--- a/script/provider/provider.lua
+++ b/script/provider/provider.lua
@@ -1395,7 +1395,7 @@ m.register 'inlayHint/resolve' {
 }
 
 m.register 'textDocument/diagnostic' {
-    preview = true,
+    -- preview = true,
     capability = {
         diagnosticProvider = {
             identifier            = 'identifier',
@@ -1408,30 +1408,23 @@ m.register 'textDocument/diagnostic' {
         local uri = files.getRealUri(params.textDocument.uri)
         workspace.awaitReady(uri)
         local core = require 'provider.diagnostic'
-        -- TODO: do some trick
-        core.doDiagnostic(uri)
 
-        return {
-            kind = 'unchanged',
-            resultId = uri,
-        }
-
-        --if not params.previousResultId then
-        --    core.clearCache(uri)
-        --end
-        --local results, unchanged = core.pullDiagnostic(uri, false)
-        --if unchanged then
-        --    return {
-        --        kind = 'unchanged',
-        --        resultId = uri,
-        --    }
-        --else
-        --    return {
-        --        kind = 'full',
-        --        resultId = uri,
-        --        items = results or {},
-        --    }
-        --end
+        if not params.previousResultId then
+           core.clear(uri)
+        end
+        local results, unchanged = core.pullDiagnostic(uri, false)
+        if unchanged then
+           return {
+               kind = 'unchanged',
+               resultId = uri,
+           }
+        else
+           return {
+               kind = 'full',
+               resultId = uri,
+               items = results or {},
+           }
+        end
     end
 }
 
@@ -1450,6 +1443,7 @@ m.register 'workspace/diagnostic' {
             excepts[#excepts+1] = id.value
         end
         core.clearCacheExcept(excepts)
+        local items = {}
         local function convertItem(result)
             if result.unchanged then
                 return {
@@ -1468,17 +1462,20 @@ m.register 'workspace/diagnostic' {
                 }
             end
         end
-        core.pullDiagnosticScope(function (result)
-            proto.notify('$/progress', {
-                token = params.partialResultToken,
-                value = {
-                    items = {
-                        convertItem(result)
-                    }
-                }
-            })
+        await.await(function()
+            core.pullDiagnosticScope(function (result)
+                table.insert(items, convertItem(result))
+                -- proto.notify('$/progress', {
+                --     token = params.partialResultToken,
+                --     value = {
+                --         items = {
+                --             convertItem(result)
+                --         }
+                --     }
+                -- })
+            end)
         end)
-        return { items = {} }
+        return { items = items }
     end
 }
 

From d85abbc63087f862dbbfedc16052f3b2e6ebc1ce Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 13 Feb 2023 14:57:51 +0100
Subject: [PATCH 21/48] Use older setup-luamake

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 540b16dcd..418da1847 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -28,7 +28,7 @@ jobs:
       - uses: actions/checkout@v2
         with:
           submodules: recursive
-      - uses: actboy168/setup-luamake@master
+      - uses: actboy168/setup-luamake@9cfeef6
       - run: luamake -platform ${{ matrix.platform }}
 
       - name: Create bin archive

From 9785f4df905ccfa74e7fa7e5e0bde22b5867a64f Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 13 Feb 2023 14:57:51 +0100
Subject: [PATCH 22/48] Use older setup-luamake

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 418da1847..ed240398b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -28,7 +28,7 @@ jobs:
       - uses: actions/checkout@v2
         with:
           submodules: recursive
-      - uses: actboy168/setup-luamake@9cfeef6
+      - uses: actboy168/setup-luamake@9cfeef6b19646266c655c14c16f7db78446767ab
       - run: luamake -platform ${{ matrix.platform }}
 
       - name: Create bin archive

From b260868340718bee78c29259c79b000a50fa18b0 Mon Sep 17 00:00:00 2001
From: vlaaad <v.proczenko@gmail.com>
Date: Mon, 17 Apr 2023 10:44:15 +0200
Subject: [PATCH 23/48] Update README.md

---
 README.md | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/README.md b/README.md
index 652ac4db4..b558a6608 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,29 @@
+# [Defold](https://defold.com/)'s fork of lua language server
+
+This language server works from the Defold editor and can be used to lint Defold projects.
+
+## Notable changes
+
+1. Add build a defold library project and a build script that assembles it on [release](https://github.com/defold/lua-language-server/releases) — use `release.zip` links as dependencies.
+2. Add text document and workspace diagnostics.
+
+## Known issues
+
+EmmyLua might [trip over the source code](https://github.com/EmmyLua/VSCode-EmmyLua/issues/123) of this project when it's extracted by the Defold editor to `/build/plugins`. The solution is to configure EmmyLua to ignore the build folder with this `emmy.config.json` file:
+```json
+{
+    "source": [
+        {
+            "dir": ".",
+            "exclude": [
+                "configs/**",
+                "build/**"
+            ]
+        }
+    ]
+}
+```
+
 # lua-language-server
 
 ![build](https://github.com/sumneko/lua-language-server/workflows/build/badge.svg)

From c74074f753523a86578ed83b833a30e004ee5b2d Mon Sep 17 00:00:00 2001
From: vlaaad <v.proczenko@gmail.com>
Date: Mon, 17 Apr 2023 10:47:12 +0200
Subject: [PATCH 24/48] Update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index b558a6608..9a1102234 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ This language server works from the Defold editor and can be used to lint Defold
 
 1. Add build a defold library project and a build script that assembles it on [release](https://github.com/defold/lua-language-server/releases) — use `release.zip` links as dependencies.
 2. Add text document and workspace diagnostics.
+3. Use LuaCheck for diagnostics.
 
 ## Known issues
 

From bdf9d33cb5646ddce6e3985dad071ec6f84e8a7c Mon Sep 17 00:00:00 2001
From: vlaaad <v.proczenko@gmail.com>
Date: Mon, 17 Apr 2023 10:47:48 +0200
Subject: [PATCH 25/48] Update README.md

---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index 9a1102234..b856eb6d0 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,6 @@ EmmyLua might [trip over the source code](https://github.com/EmmyLua/VSCode-Emmy
         {
             "dir": ".",
             "exclude": [
-                "configs/**",
                 "build/**"
             ]
         }

From 351e43ddba15e157406c4eecbfcf41ddf269f6c2 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 24 Aug 2023 10:42:02 +0200
Subject: [PATCH 26/48] Add arm64-macos build

---
 .github/workflows/build.yml | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ed240398b..aa72ebedf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -20,8 +20,9 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-18.04,   target: linux,   platform: linux-x64, zip: x86_64-linux    }
+          - { os: ubuntu-latest,  target: linux,   platform: linux-x64, zip: x86_64-linux    }
           - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
+          - { os: macos-11,       target: darwin,  platform: darwin-arm64, zip: arm64-macos  }
           - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }
     runs-on: ${{ matrix.os }}
     steps:
@@ -68,6 +69,12 @@ jobs:
         shell: bash
         run: |
 
+          7z -y x arm64-macos/arm64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/arm64-macos
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a arm64-macos.zip bin
+          rm -r bin
+          cd -
+
           7z -y x x86_64-macos/x86_64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-macos
           cd defold/lsp-lua-language-server/plugins
           7z -y a x86_64-macos.zip bin

From 3a97e9196a853aebf880e7ce2acfd9b857c77c25 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 14:40:07 +0200
Subject: [PATCH 27/48] Merge

---
 .editorconfig                                 |  196 +-
 .github/ISSUE_TEMPLATE/bug-report.yml         |    2 +-
 .github/workflows/build.yml                   |   14 +-
 .gitignore                                    |    1 +
 .gitmodules                                   |    3 +
 .luarc.json                                   |    5 +-
 .vscode/launch.json                           |   15 +-
 .vscode/settings.json                         |    1 +
 3rd/json.lua                                  |    1 +
 README.md                                     |   99 +-
 changelog.md                                  |  984 ++++--
 doc/en-us/config.md                           |  158 +-
 doc/pt-br/config.md                           |  162 +-
 doc/zh-cn/config.md                           |  162 +-
 doc/zh-tw/config.md                           |  158 +-
 locale/en-us/meta.lua                         |   10 +-
 locale/en-us/script.lua                       |  143 +-
 locale/en-us/setting.lua                      |   39 +-
 locale/pt-br/meta.lua                         |   10 +-
 locale/pt-br/script.lua                       |  135 +-
 locale/pt-br/setting.lua                      |   39 +-
 locale/zh-cn/meta.lua                         |   10 +-
 locale/zh-cn/script.lua                       |  139 +-
 locale/zh-cn/setting.lua                      |   39 +-
 locale/zh-tw/meta.lua                         |   10 +-
 locale/zh-tw/script.lua                       |  135 +-
 locale/zh-tw/setting.lua                      |   39 +-
 main.lua                                      |    5 +-
 make.bat                                      |    5 +
 make.lua                                      |   55 +-
 make.sh                                       |    7 +
 make/code_format.lua                          |   25 +-
 make/modules.cpp                              |    2 +-
 meta/3rd/OpenResty/library/prometheus.lua     |    2 +-
 meta/3rd/OpenResty/library/resty/signal.lua   |   81 +-
 meta/3rd/bee/config.json                      |    4 +
 meta/3rd/bee/library/bee/filesystem.lua       |  240 ++
 meta/3rd/bee/library/bee/platform.lua         |   62 +
 meta/3rd/bee/library/bee/subprocess.lua       |   99 +
 meta/3rd/bee/library/bee/thread.lua           |   62 +
 meta/3rd/example/plugin.lua                   |    2 +-
 meta/3rd/ffi-reflect/config.json              |    3 +
 meta/3rd/ffi-reflect/library/reflect.lua      |  128 +
 meta/3rd/love2d/config.json                   |    5 +-
 meta/3rd/love2d/library/love.lua              |   48 +-
 meta/3rd/love2d/library/love/event.lua        |    2 +-
 meta/3rd/love2d/library/love/graphics.lua     |   41 +-
 meta/3rd/love2d/library/love/math.lua         |   31 +-
 meta/3rd/love2d/library/love/physics.lua      |   18 +-
 meta/3rd/lovr/library/callback.lua            |   18 +
 meta/3rd/lovr/library/lovr/audio.lua          |   92 +-
 meta/3rd/lovr/library/lovr/data.lua           |   33 +-
 meta/3rd/lovr/library/lovr/filesystem.lua     |   42 +-
 meta/3rd/lovr/library/lovr/graphics.lua       |  503 ++-
 meta/3rd/lovr/library/lovr/headset.lua        |   67 +-
 meta/3rd/lovr/library/lovr/math.lua           |  163 +-
 meta/3rd/lovr/library/lovr/physics.lua        |  134 +-
 meta/3rd/lovr/library/lovr/system.lua         |   26 +
 meta/3rd/lovr/library/lovr/thread.lua         |    4 +-
 meta/3rd/luv/config.json                      |    8 +
 meta/3rd/luv/library/errors.lua               |  261 ++
 meta/3rd/luv/library/luv_dir_t.lua            |   34 +
 meta/3rd/luv/library/luv_thread_t.lua         |   20 +
 meta/3rd/luv/library/luv_work_ctx_t.lua       |   17 +
 meta/3rd/luv/library/types.lua                |  526 +++
 meta/3rd/luv/library/uv.lua                   | 3075 +++++++++++++++++
 meta/3rd/luv/library/uv_async_t.lua           |   35 +
 meta/3rd/luv/library/uv_check_t.lua           |   29 +
 meta/3rd/luv/library/uv_fs_event_t.lua        |   33 +
 meta/3rd/luv/library/uv_fs_poll_t.lua         |   35 +
 meta/3rd/luv/library/uv_handle_t.lua          |  132 +
 meta/3rd/luv/library/uv_idle_t.lua            |   38 +
 meta/3rd/luv/library/uv_pipe_t.lua            |  103 +
 meta/3rd/luv/library/uv_poll_t.lua            |   57 +
 meta/3rd/luv/library/uv_prepare_t.lua         |   30 +
 meta/3rd/luv/library/uv_process_t.lua         |   22 +
 meta/3rd/luv/library/uv_req_t.lua             |   25 +
 meta/3rd/luv/library/uv_signal_t.lua          |   69 +
 meta/3rd/luv/library/uv_stream_t.lua          |  173 +
 meta/3rd/luv/library/uv_tcp_t.lua             |  112 +
 meta/3rd/luv/library/uv_timer_t.lua           |   55 +
 meta/3rd/luv/library/uv_tty_t.lua             |   44 +
 meta/3rd/luv/library/uv_udp_t.lua             |  175 +
 meta/3rd/skynet/library/skynet.lua            |    9 +-
 meta/spell/lua_dict.txt                       |   12 +-
 meta/template/basic.lua                       |  105 +-
 meta/template/bit.lua                         |    2 +-
 meta/template/bit32.lua                       |    2 +-
 meta/template/builtin.lua                     |    2 +-
 meta/template/coroutine.lua                   |    3 +-
 meta/template/debug.lua                       |   26 +-
 meta/template/ffi.lua                         |    2 +-
 meta/template/io.lua                          |   66 +-
 meta/template/jit.lua                         |   24 +-
 meta/template/jit.profile.lua                 |   20 +
 meta/template/jit.util.lua                    |  120 +
 meta/template/math.lua                        |    7 +-
 meta/template/os.lua                          |   36 +-
 meta/template/package.lua                     |    2 +-
 meta/template/string.buffer.lua               |    2 +-
 meta/template/string.lua                      |   56 +-
 meta/template/table.clear.lua                 |    4 +-
 meta/template/table.lua                       |    6 +-
 meta/template/table.new.lua                   |    4 +-
 meta/template/utf8.lua                        |    2 +-
 meta/whimsical/basic.lua                      |   38 +
 meta/whimsical/builtin.lua                    |   18 +
 meta/whimsical/rule.lua                       |  122 +
 script/await.lua                              |    5 +
 script/brave/work.lua                         |   59 +-
 script/cli/check.lua                          |   13 +-
 script/cli/doc.lua                            |  213 +-
 script/cli/doc2md.lua                         |   64 +-
 script/cli/init.lua                           |    2 +-
 script/client.lua                             |  129 +-
 script/config/loader.lua                      |    2 +-
 script/config/template.lua                    |   17 +-
 script/core/code-action.lua                   |   56 +-
 script/core/code-lens.lua                     |  142 +
 script/core/command/autoRequire.lua           |   11 +-
 script/core/command/exportDocument.lua        |   16 +
 script/core/command/getConfig.lua             |   13 +
 script/core/command/reloadFFIMeta.lua         |   56 +
 script/core/command/setConfig.lua             |   10 +-
 script/core/completion/auto-require.lua       |   24 +-
 script/core/completion/completion.lua         |  183 +-
 script/core/completion/keyword.lua            |   81 +
 script/core/completion/postfix.lua            |   33 +
 script/core/definition.lua                    |    4 +-
 .../core/diagnostics/assign-type-mismatch.lua |    2 +-
 script/core/diagnostics/cast-local-type.lua   |    5 +-
 .../core/diagnostics/cast-type-mismatch.lua   |    6 +-
 .../core/diagnostics/duplicate-set-field.lua  |   25 +
 script/core/diagnostics/global-element.lua    |   57 +
 script/core/diagnostics/global-in-nil-env.lua |    3 +
 .../diagnostics/helper/missing-doc-helper.lua |   83 +
 .../diagnostics/incomplete-signature-doc.lua  |  109 +
 script/core/diagnostics/inject-field.lua      |  147 +
 script/core/diagnostics/missing-fields.lua    |   84 +
 .../core/diagnostics/missing-global-doc.lua   |   27 +
 .../diagnostics/missing-local-export-doc.lua  |   52 +
 script/core/diagnostics/missing-return.lua    |    2 +-
 script/core/diagnostics/name-style-check.lua  |   35 +
 .../core/diagnostics/param-type-mismatch.lua  |   24 +-
 script/core/diagnostics/undefined-field.lua   |    8 -
 script/core/diagnostics/undefined-global.lua  |   46 +-
 .../diagnostics/unknown-cast-variable.lua     |   10 +-
 script/core/diagnostics/unreachable-code.lua  |    2 +-
 script/core/hover/description.lua             |   72 +-
 script/core/hover/init.lua                    |   11 +-
 script/core/hover/label.lua                   |   10 +-
 script/core/reference.lua                     |    7 +-
 script/core/rename.lua                        |   29 +-
 script/core/semantic-tokens.lua               |   71 +-
 script/core/signature.lua                     |   41 +-
 script/core/workspace-symbol.lua              |    2 +-
 script/file-uri.lua                           |    7 +-
 script/files.lua                              |  121 +-
 script/filewatch.lua                          |   50 +-
 script/gc.lua                                 |    4 +-
 script/glob/gitignore.lua                     |    6 +-
 script/global.d.lua                           |   16 +
 script/json-beautify.lua                      |   23 +-
 script/json-edit.lua                          |  191 +-
 script/json.lua                               |   16 +-
 script/jsonrpc.lua                            |   13 +-
 script/library.lua                            |   28 +-
 script/log.lua                                |    2 +-
 script/meta/bee/filesystem.lua                |    6 +-
 script/meta/bee/filewatch.lua                 |   32 +
 script/meta/bee/socket.lua                    |   69 +
 script/meta/bee/thread.lua                    |   33 +
 script/parser/compile.lua                     |  137 +-
 script/parser/guide.lua                       |  191 +-
 script/parser/luadoc.lua                      |   95 +-
 script/plugin.lua                             |    1 +
 script/plugins/ffi/c-parser/c99.lua           |  731 ++++
 script/plugins/ffi/c-parser/cdefines.lua      |  152 +
 script/plugins/ffi/c-parser/cdriver.lua       |   54 +
 script/plugins/ffi/c-parser/cpp.lua           |  869 +++++
 script/plugins/ffi/c-parser/ctypes.lua        |  604 ++++
 script/plugins/ffi/c-parser/typed.lua         |  172 +
 script/plugins/ffi/c-parser/util.lua          |   28 +
 script/plugins/ffi/cdefRerence.lua            |   37 +
 script/plugins/ffi/init.lua                   |  374 ++
 script/plugins/ffi/searchCode.lua             |   69 +
 script/plugins/init.lua                       |    1 +
 script/proto/converter.lua                    |   26 +
 script/proto/define.lua                       |   38 +-
 script/proto/diagnostic.lua                   |   46 +-
 script/proto/proto.lua                        |   48 +-
 script/provider/capability.lua                |    1 +
 script/provider/code-lens.lua                 |   29 +
 script/provider/diagnostic.lua                |    6 +
 script/provider/formatting.lua                |   10 +-
 script/provider/markdown.lua                  |    4 +
 script/provider/name-style.lua                |   28 +
 script/provider/provider.lua                  |  124 +-
 script/service/init.lua                       |    1 -
 script/service/service.lua                    |    7 +-
 script/service/telemetry.lua                  |  203 --
 script/string-merger.lua                      |    4 +
 script/utility.lua                            |   80 +-
 script/vm/compiler.lua                        |  585 ++--
 script/vm/def.lua                             |    4 +-
 script/vm/doc.lua                             |   72 +
 script/vm/field.lua                           |    4 +-
 script/vm/function.lua                        |   10 +-
 script/vm/generic.lua                         |    6 +-
 script/vm/global.lua                          |  258 +-
 script/vm/infer.lua                           |  153 +-
 script/vm/init.lua                            |    5 +-
 script/vm/local-id.lua                        |  242 --
 script/vm/node.lua                            |   41 +-
 script/vm/operator.lua                        |   96 +-
 script/vm/precompile.lua                      |   20 +
 script/vm/ref.lua                             |   57 +-
 script/vm/runner.lua                          |  565 ---
 script/vm/sign.lua                            |    8 +-
 script/vm/tracer.lua                          |  877 +++++
 script/vm/type.lua                            |   45 +-
 script/vm/variable.lua                        |  411 +++
 script/vm/visible.lua                         |   30 +-
 script/workspace/loading.lua                  |   21 +-
 script/workspace/require-path.lua             |   50 +-
 script/workspace/workspace.lua                |  135 +-
 test.lua                                      |    7 +-
 test/code_action/init.lua                     |  144 +-
 test/completion/common.lua                    |  323 +-
 test/crossfile/completion.lua                 |   98 +-
 test/crossfile/definition.lua                 |    1 +
 test/crossfile/diagnostic.lua                 |   25 +-
 test/crossfile/hover.lua                      |  165 +-
 test/crossfile/infer.lua                      |  165 +
 test/crossfile/init.lua                       |    1 +
 test/crossfile/references.lua                 |    5 +-
 test/definition/bug.lua                       |    7 +
 test/definition/luadoc.lua                    |   12 +
 test/diagnostics/ambiguity-1.lua              |   29 +
 test/diagnostics/assign-type-mismatch.lua     |  474 +++
 test/diagnostics/await-in-sync.lua            |  132 +
 test/diagnostics/cast-local-type.lua          |  334 ++
 test/diagnostics/cast-type-mismatch.lua       |   13 +
 test/diagnostics/circle-doc-class.lua         |   14 +
 test/diagnostics/close-non-object.lua         |   18 +
 test/diagnostics/code-after-break.lua         |    7 +
 test/diagnostics/common.lua                   | 2233 ------------
 test/diagnostics/count-down-loop.lua          |   29 +
 test/diagnostics/deprecated.lua               |   21 +
 test/diagnostics/discard-returns.lua          |   17 +
 test/diagnostics/doc-field-no-class.lua       |   16 +
 test/diagnostics/duplicate-doc-alias.lua      |   10 +
 test/diagnostics/duplicate-doc-field.lua      |   38 +
 test/diagnostics/duplicate-doc-param.lua      |   12 +
 test/diagnostics/duplicate-index.lua          |   24 +
 test/diagnostics/duplicate-set-field.lua      |   74 +
 test/diagnostics/empty-block.lua              |   32 +
 test/diagnostics/global-element.lua           |   63 +
 test/diagnostics/global-in-nil-env.lua        |   44 +
 test/diagnostics/incomplete-signature-doc.lua |  398 +++
 test/diagnostics/init.lua                     |   89 +-
 test/diagnostics/inject-field.lua             |   84 +
 test/diagnostics/invisible.lua                |  145 +
 test/diagnostics/lowercase-global.lua         |   39 +
 test/diagnostics/missing-fields.lua           |  233 ++
 test/diagnostics/missing-global-doc.lua       |  297 ++
 test/diagnostics/missing-local-export-doc.lua |  175 +
 test/diagnostics/missing-parameter.lua        |   90 +
 test/diagnostics/missing-return-value.lua     |   34 +
 test/diagnostics/missing-return.lua           |  158 +
 test/diagnostics/need-check-nil.lua           |   68 +
 test/diagnostics/newfield-call.lua            |   15 +
 test/diagnostics/newline-call.lua             |   34 +
 test/diagnostics/not-yieldable.lua            |   48 +
 test/diagnostics/param-type-mismatch.lua      |  248 ++
 test/diagnostics/redefined-local.lua          |   22 +
 test/diagnostics/redundant-parameter.lua      |  214 ++
 test/diagnostics/redundant-return-value.lua   |   32 +
 test/diagnostics/redundant-return.lua         |   34 +
 test/diagnostics/redundant-value.lua          |    7 +
 test/diagnostics/return-type-mismatch.lua     |  167 +
 test/diagnostics/trailing-space.lua           |   32 +
 test/diagnostics/type-check.lua               | 1124 ------
 test/diagnostics/unbalanced-assignments.lua   |   34 +
 test/diagnostics/undefined-doc-class.lua      |    3 +
 test/diagnostics/undefined-doc-name.lua       |   19 +
 test/diagnostics/undefined-doc-param.lua      |   21 +
 test/diagnostics/undefined-env-child.lua      |   36 +
 test/diagnostics/undefined-field.lua          |  148 +
 test/diagnostics/undefined-global.lua         |   36 +
 test/diagnostics/unknown-cast-variable.lua    |    8 +
 test/diagnostics/unknown-diag-code.lua        |    3 +
 test/diagnostics/unknown-operator.lua         |    4 +
 test/diagnostics/unreachable-code.lua         |   71 +
 test/diagnostics/unused-function.lua          |   48 +
 test/diagnostics/unused-label.lua             |    3 +
 test/diagnostics/unused-local.lua             |   99 +
 test/diagnostics/unused-vararg.lua            |    6 +
 test/full/init.lua                            |    2 +-
 test/full/projects.lua                        |    2 +-
 test/full/self.lua                            |    4 +-
 test/hover/init.lua                           |  102 +-
 test/other/filewatch.lua                      |    2 +-
 test/plugins/ffi/builder.lua                  |  233 ++
 test/plugins/ffi/cdef.lua                     |   61 +
 test/plugins/ffi/parser.lua                   |  176 +
 test/plugins/ffi/test.lua                     |   38 +
 test/references/all.lua                       |   13 +
 test/references/common.lua                    |   15 +
 test/rename/init.lua                          |   34 +
 test/signature/init.lua                       |   72 +-
 .../tclient/tests/change-workspace-folder.lua |   18 +
 test/tclient/tests/modify-luarc.lua           |   56 +-
 test/tclient/tests/recursive-runner.lua       |    3 +-
 test/type_inference/init.lua                  |  412 ++-
 theme-tokens.md                               |   86 +-
 tools/build-doc.lua                           |   24 +-
 tools/configuration.lua                       |    8 +-
 tools/love-api.lua                            |    2 +-
 tools/lovr-api.lua                            |    2 +-
 320 files changed, 24184 insertions(+), 7195 deletions(-)
 create mode 160000 3rd/json.lua
 create mode 100644 make.bat
 create mode 100755 make.sh
 create mode 100644 meta/3rd/bee/config.json
 create mode 100644 meta/3rd/bee/library/bee/filesystem.lua
 create mode 100644 meta/3rd/bee/library/bee/platform.lua
 create mode 100644 meta/3rd/bee/library/bee/subprocess.lua
 create mode 100644 meta/3rd/bee/library/bee/thread.lua
 create mode 100644 meta/3rd/ffi-reflect/config.json
 create mode 100644 meta/3rd/ffi-reflect/library/reflect.lua
 create mode 100644 meta/3rd/luv/config.json
 create mode 100644 meta/3rd/luv/library/errors.lua
 create mode 100644 meta/3rd/luv/library/luv_dir_t.lua
 create mode 100644 meta/3rd/luv/library/luv_thread_t.lua
 create mode 100644 meta/3rd/luv/library/luv_work_ctx_t.lua
 create mode 100644 meta/3rd/luv/library/types.lua
 create mode 100644 meta/3rd/luv/library/uv.lua
 create mode 100644 meta/3rd/luv/library/uv_async_t.lua
 create mode 100644 meta/3rd/luv/library/uv_check_t.lua
 create mode 100644 meta/3rd/luv/library/uv_fs_event_t.lua
 create mode 100644 meta/3rd/luv/library/uv_fs_poll_t.lua
 create mode 100644 meta/3rd/luv/library/uv_handle_t.lua
 create mode 100644 meta/3rd/luv/library/uv_idle_t.lua
 create mode 100644 meta/3rd/luv/library/uv_pipe_t.lua
 create mode 100644 meta/3rd/luv/library/uv_poll_t.lua
 create mode 100644 meta/3rd/luv/library/uv_prepare_t.lua
 create mode 100644 meta/3rd/luv/library/uv_process_t.lua
 create mode 100644 meta/3rd/luv/library/uv_req_t.lua
 create mode 100644 meta/3rd/luv/library/uv_signal_t.lua
 create mode 100644 meta/3rd/luv/library/uv_stream_t.lua
 create mode 100644 meta/3rd/luv/library/uv_tcp_t.lua
 create mode 100644 meta/3rd/luv/library/uv_timer_t.lua
 create mode 100644 meta/3rd/luv/library/uv_tty_t.lua
 create mode 100644 meta/3rd/luv/library/uv_udp_t.lua
 create mode 100644 meta/template/jit.profile.lua
 create mode 100644 meta/template/jit.util.lua
 create mode 100644 meta/whimsical/basic.lua
 create mode 100644 meta/whimsical/builtin.lua
 create mode 100644 meta/whimsical/rule.lua
 create mode 100644 script/core/code-lens.lua
 create mode 100644 script/core/command/exportDocument.lua
 create mode 100644 script/core/command/getConfig.lua
 create mode 100644 script/core/command/reloadFFIMeta.lua
 create mode 100644 script/core/diagnostics/global-element.lua
 create mode 100644 script/core/diagnostics/helper/missing-doc-helper.lua
 create mode 100644 script/core/diagnostics/incomplete-signature-doc.lua
 create mode 100644 script/core/diagnostics/inject-field.lua
 create mode 100644 script/core/diagnostics/missing-fields.lua
 create mode 100644 script/core/diagnostics/missing-global-doc.lua
 create mode 100644 script/core/diagnostics/missing-local-export-doc.lua
 create mode 100644 script/core/diagnostics/name-style-check.lua
 create mode 100644 script/meta/bee/filewatch.lua
 create mode 100644 script/meta/bee/socket.lua
 create mode 100644 script/meta/bee/thread.lua
 create mode 100644 script/plugins/ffi/c-parser/c99.lua
 create mode 100644 script/plugins/ffi/c-parser/cdefines.lua
 create mode 100644 script/plugins/ffi/c-parser/cdriver.lua
 create mode 100644 script/plugins/ffi/c-parser/cpp.lua
 create mode 100644 script/plugins/ffi/c-parser/ctypes.lua
 create mode 100644 script/plugins/ffi/c-parser/typed.lua
 create mode 100644 script/plugins/ffi/c-parser/util.lua
 create mode 100644 script/plugins/ffi/cdefRerence.lua
 create mode 100644 script/plugins/ffi/init.lua
 create mode 100644 script/plugins/ffi/searchCode.lua
 create mode 100644 script/plugins/init.lua
 create mode 100644 script/provider/code-lens.lua
 create mode 100644 script/provider/name-style.lua
 delete mode 100644 script/service/telemetry.lua
 delete mode 100644 script/vm/local-id.lua
 create mode 100644 script/vm/precompile.lua
 delete mode 100644 script/vm/runner.lua
 create mode 100644 script/vm/tracer.lua
 create mode 100644 script/vm/variable.lua
 create mode 100644 test/crossfile/infer.lua
 create mode 100644 test/diagnostics/ambiguity-1.lua
 create mode 100644 test/diagnostics/assign-type-mismatch.lua
 create mode 100644 test/diagnostics/await-in-sync.lua
 create mode 100644 test/diagnostics/cast-local-type.lua
 create mode 100644 test/diagnostics/cast-type-mismatch.lua
 create mode 100644 test/diagnostics/circle-doc-class.lua
 create mode 100644 test/diagnostics/close-non-object.lua
 create mode 100644 test/diagnostics/code-after-break.lua
 delete mode 100644 test/diagnostics/common.lua
 create mode 100644 test/diagnostics/count-down-loop.lua
 create mode 100644 test/diagnostics/deprecated.lua
 create mode 100644 test/diagnostics/discard-returns.lua
 create mode 100644 test/diagnostics/doc-field-no-class.lua
 create mode 100644 test/diagnostics/duplicate-doc-alias.lua
 create mode 100644 test/diagnostics/duplicate-doc-field.lua
 create mode 100644 test/diagnostics/duplicate-doc-param.lua
 create mode 100644 test/diagnostics/duplicate-index.lua
 create mode 100644 test/diagnostics/duplicate-set-field.lua
 create mode 100644 test/diagnostics/empty-block.lua
 create mode 100644 test/diagnostics/global-element.lua
 create mode 100644 test/diagnostics/global-in-nil-env.lua
 create mode 100644 test/diagnostics/incomplete-signature-doc.lua
 create mode 100644 test/diagnostics/inject-field.lua
 create mode 100644 test/diagnostics/invisible.lua
 create mode 100644 test/diagnostics/lowercase-global.lua
 create mode 100644 test/diagnostics/missing-fields.lua
 create mode 100644 test/diagnostics/missing-global-doc.lua
 create mode 100644 test/diagnostics/missing-local-export-doc.lua
 create mode 100644 test/diagnostics/missing-parameter.lua
 create mode 100644 test/diagnostics/missing-return-value.lua
 create mode 100644 test/diagnostics/missing-return.lua
 create mode 100644 test/diagnostics/need-check-nil.lua
 create mode 100644 test/diagnostics/newfield-call.lua
 create mode 100644 test/diagnostics/newline-call.lua
 create mode 100644 test/diagnostics/not-yieldable.lua
 create mode 100644 test/diagnostics/param-type-mismatch.lua
 create mode 100644 test/diagnostics/redefined-local.lua
 create mode 100644 test/diagnostics/redundant-parameter.lua
 create mode 100644 test/diagnostics/redundant-return-value.lua
 create mode 100644 test/diagnostics/redundant-return.lua
 create mode 100644 test/diagnostics/redundant-value.lua
 create mode 100644 test/diagnostics/return-type-mismatch.lua
 create mode 100644 test/diagnostics/trailing-space.lua
 delete mode 100644 test/diagnostics/type-check.lua
 create mode 100644 test/diagnostics/unbalanced-assignments.lua
 create mode 100644 test/diagnostics/undefined-doc-class.lua
 create mode 100644 test/diagnostics/undefined-doc-name.lua
 create mode 100644 test/diagnostics/undefined-doc-param.lua
 create mode 100644 test/diagnostics/undefined-env-child.lua
 create mode 100644 test/diagnostics/undefined-field.lua
 create mode 100644 test/diagnostics/undefined-global.lua
 create mode 100644 test/diagnostics/unknown-cast-variable.lua
 create mode 100644 test/diagnostics/unknown-diag-code.lua
 create mode 100644 test/diagnostics/unknown-operator.lua
 create mode 100644 test/diagnostics/unreachable-code.lua
 create mode 100644 test/diagnostics/unused-function.lua
 create mode 100644 test/diagnostics/unused-label.lua
 create mode 100644 test/diagnostics/unused-local.lua
 create mode 100644 test/diagnostics/unused-vararg.lua
 create mode 100644 test/plugins/ffi/builder.lua
 create mode 100644 test/plugins/ffi/cdef.lua
 create mode 100644 test/plugins/ffi/parser.lua
 create mode 100644 test/plugins/ffi/test.lua

diff --git a/.editorconfig b/.editorconfig
index 48df1807b..e432c7eb0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,90 +1,128 @@
+
 # 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 <const> = 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 crlf/lf/cr/auto, if it is 'auto', in windows it is crlf other platforms are lf
+# in neovim the value 'auto' is not a valid option, please use 'unset' 
+end_of_line = auto
+
+#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/'<const string>', 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/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 aa72ebedf..f9d19a3b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,5 +1,8 @@
 name: build
 
+permissions:
+  contents: write
+
 on:
   push:
     branches:
@@ -51,10 +54,13 @@ jobs:
 
           7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
 
-          echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
-          echo ::set-output name=PKG_NAME::${PKG_NAME}
-      
-      - 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: |
diff --git a/.gitignore b/.gitignore
index ea8826788..4d868571c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
 /meta/*
 !/meta/template
 !/meta/3rd
+!/meta/whimsical
 /bin*
diff --git a/.gitmodules b/.gitmodules
index ef37cab50..ce20bc394 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -19,3 +19,6 @@
 [submodule "3rd/luafilesystem"]
 	path = 3rd/luafilesystem
 	url = https://github.com/lunarmodules/luafilesystem
+[submodule "3rd/json.lua"]
+	path = 3rd/json.lua
+	url = https://github.com/actboy168/json.lua
diff --git a/.luarc.json b/.luarc.json
index 474062724..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",
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 2b077bee7..8c86fcbee 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",
 			"sourceCoding": "utf8",
-			"console": "internalConsole",
+			"console": "integratedTerminal",
+			"internalConsoleOptions": "openOnSessionStart",
 			"outputCapture": [
 				"print",
 				"stderr",
@@ -36,9 +34,6 @@
 				]
 			],
 			"windows": {
-				"name": "🍄attach",
-				"type": "lua",
-				"request": "attach",
 				"sourceMaps": [
 					[
 						"script\\*",
@@ -55,6 +50,8 @@
 			"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",
@@ -72,6 +69,8 @@
 			"luaexe": "${workspaceFolder}/bin/lua-language-server",
 			"program": "${workspaceRoot}/tools/locale.lua",
 			"cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so",
+			"console": "integratedTerminal",
+			"internalConsoleOptions": "openOnSessionStart",
 			"arg": [
 			],
 			"luaVersion": "latest",
@@ -89,6 +88,8 @@
 			"luaexe": "${workspaceFolder}/bin/lua-language-server",
 			"program": "${workspaceRoot}/tools/build-doc.lua",
 			"cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so",
+			"console": "integratedTerminal",
+			"internalConsoleOptions": "openOnSessionStart",
 			"arg": [
 			],
 			"luaVersion": "latest",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 74e817889..5eea795aa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,6 +5,7 @@
         "--develop=true",
         "--dbgport=11413",
         "--loglevel=trace",
+        "--shownode",
         //"--lazy",
     ],
 }
diff --git a/3rd/json.lua b/3rd/json.lua
new file mode 160000
index 000000000..bd7b7787b
--- /dev/null
+++ b/3rd/json.lua
@@ -0,0 +1 @@
+Subproject commit bd7b7787bb8b586e59b5afe5886dd1b76c86eb56
diff --git a/README.md b/README.md
index b856eb6d0..066c11531 100644
--- a/README.md
+++ b/README.md
@@ -26,72 +26,64 @@ EmmyLua might [trip over the source code](https://github.com/EmmyLua/VSCode-Emmy
 
 # 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
 
@@ -101,26 +93,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
@@ -132,17 +114,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://user-images.githubusercontent.com/61925890/183228083-d3aa4eca-30c7-4b9f-aaab-26ce3d8a14fb.png
diff --git a/changelog.md b/changelog.md
index b48657815..85fee49c5 100644
--- a/changelog.md
+++ b/changelog.md
@@ -2,18 +2,266 @@
 
 ## Unreleased
 * `NEW` Added LuaCheck
+## 3.7.1
+* `FIX` [#2299]
+
+[#2299]: https://github.com/LuaLS/lua-language-server/issues/2299
+
+## 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
 
-[#831]:  https://github.com/sumneko/lua-language-server/issues/831
-[#1729]: https://github.com/sumneko/lua-language-server/issues/1729
-[#1751]: https://github.com/sumneko/lua-language-server/issues/1751
-
-`2022-11-29`
 ## 3.6.4
+`2022-11-29`
 * `NEW` modify `require` after renaming files
 * `FIX` circulation reference in process analysis
   ```lua
@@ -31,17 +279,17 @@
 * `FIX` [#1704]
 * `FIX` [#1717]
 
-[#1698]: https://github.com/sumneko/lua-language-server/issues/1698
-[#1704]: https://github.com/sumneko/lua-language-server/issues/1704
-[#1717]: https://github.com/sumneko/lua-language-server/issues/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/sumneko/lua-language-server/issues/1684
-[#1692]: https://github.com/sumneko/lua-language-server/issues/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`
@@ -51,10 +299,10 @@
 * `FIX` [#1679]
 * `FIX` [#1680]
 
-[#1676]: https://github.com/sumneko/lua-language-server/issues/1676
-[#1677]: https://github.com/sumneko/lua-language-server/issues/1677
-[#1679]: https://github.com/sumneko/lua-language-server/issues/1679
-[#1680]: https://github.com/sumneko/lua-language-server/issues/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`
@@ -63,7 +311,7 @@
 * `FIX` description disapeared for overloaded function
 * `FIX` [#1675]
 
-[#1675]: https://github.com/sumneko/lua-language-server/issues/1675
+[#1675]: https://github.com/LuaLS/lua-language-server/issues/1675
 
 ## 3.6.0
 `2022-11-8`
@@ -97,8 +345,8 @@
   * `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`.  
+* `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`
@@ -227,100 +475,100 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `FIX` [#1662]
 * `FIX` [#1672]
 
-[#1153]: https://github.com/sumneko/lua-language-server/issues/1153
-[#1177]: https://github.com/sumneko/lua-language-server/issues/1177
-[#1201]: https://github.com/sumneko/lua-language-server/issues/1201
-[#1202]: https://github.com/sumneko/lua-language-server/issues/1202
-[#1332]: https://github.com/sumneko/lua-language-server/issues/1332
-[#1344]: https://github.com/sumneko/lua-language-server/issues/1344
-[#1374]: https://github.com/sumneko/lua-language-server/issues/1374
-[#1434]: https://github.com/sumneko/lua-language-server/issues/1434
-[#1457]: https://github.com/sumneko/lua-language-server/issues/1457
-[#1458]: https://github.com/sumneko/lua-language-server/issues/1458
-[#1479]: https://github.com/sumneko/lua-language-server/issues/1479
-[#1480]: https://github.com/sumneko/lua-language-server/issues/1480
-[#1484]: https://github.com/sumneko/lua-language-server/issues/1484
-[#1533]: https://github.com/sumneko/lua-language-server/issues/1533
-[#1557]: https://github.com/sumneko/lua-language-server/issues/1557
-[#1558]: https://github.com/sumneko/lua-language-server/issues/1558
-[#1561]: https://github.com/sumneko/lua-language-server/issues/1561
-[#1567]: https://github.com/sumneko/lua-language-server/issues/1567
-[#1575]: https://github.com/sumneko/lua-language-server/issues/1575
-[#1582]: https://github.com/sumneko/lua-language-server/issues/1582
-[#1593]: https://github.com/sumneko/lua-language-server/issues/1593
-[#1595]: https://github.com/sumneko/lua-language-server/issues/1595
-[#1599]: https://github.com/sumneko/lua-language-server/issues/1599
-[#1606]: https://github.com/sumneko/lua-language-server/issues/1606
-[#1608]: https://github.com/sumneko/lua-language-server/issues/1608
-[#1626]: https://github.com/sumneko/lua-language-server/issues/1626
-[#1637]: https://github.com/sumneko/lua-language-server/issues/1637
-[#1640]: https://github.com/sumneko/lua-language-server/issues/1640
-[#1641]: https://github.com/sumneko/lua-language-server/issues/1641
-[#1642]: https://github.com/sumneko/lua-language-server/issues/1642
-[#1662]: https://github.com/sumneko/lua-language-server/issues/1662
-[#1663]: https://github.com/sumneko/lua-language-server/issues/1663
-[#1670]: https://github.com/sumneko/lua-language-server/issues/1670
-[#1672]: https://github.com/sumneko/lua-language-server/issues/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/sumneko/lua-language-server/issues/1439)
-* `FIX` [#1467](https://github.com/sumneko/lua-language-server/issues/1467)
-* `FIX` [#1506](https://github.com/sumneko/lua-language-server/issues/1506)
-* `FIX` [#1537](https://github.com/sumneko/lua-language-server/issues/1537)
+* `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/sumneko/lua-language-server/issues/1529)
-* `FIX` [#1530](https://github.com/sumneko/lua-language-server/issues/1530)
+* `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/sumneko/lua-language-server/issues/1476)
-* `FIX` [#1490](https://github.com/sumneko/lua-language-server/issues/1490)
-* `FIX` [#1493](https://github.com/sumneko/lua-language-server/issues/1493)
-* `FIX` [#1499](https://github.com/sumneko/lua-language-server/issues/1499)
-* `FIX` [#1526](https://github.com/sumneko/lua-language-server/issues/1526)
+* `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/sumneko/lua-language-server/issues/1409)
-* `FIX` [#1422](https://github.com/sumneko/lua-language-server/issues/1422)
-* `FIX` [#1425](https://github.com/sumneko/lua-language-server/issues/1425)
-* `FIX` [#1428](https://github.com/sumneko/lua-language-server/issues/1428)
-* `FIX` [#1430](https://github.com/sumneko/lua-language-server/issues/1430)
-* `FIX` [#1431](https://github.com/sumneko/lua-language-server/issues/1431)
-* `FIX` [#1446](https://github.com/sumneko/lua-language-server/issues/1446)
-* `FIX` [#1451](https://github.com/sumneko/lua-language-server/issues/1451)
-* `FIX` [#1461](https://github.com/sumneko/lua-language-server/issues/1461)
-* `FIX` [#1463](https://github.com/sumneko/lua-language-server/issues/1463)
+* `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`
@@ -383,18 +631,18 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   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`
@@ -403,8 +651,8 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -418,13 +666,13 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   ---@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`
@@ -472,20 +720,20 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -530,15 +778,15 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   ```
 * `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`
@@ -550,13 +798,13 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -564,15 +812,15 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -614,10 +862,10 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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
@@ -627,90 +875,90 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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<k, v>`
-* `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>[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`
@@ -721,13 +969,13 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -738,17 +986,17 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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
 
@@ -758,25 +1006,25 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 
 ## 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
 
@@ -792,7 +1040,7 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   + `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
@@ -817,20 +1065,20 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 
 ## 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`
@@ -840,31 +1088,31 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   + `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`
@@ -873,31 +1121,31 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 ## 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`
@@ -950,18 +1198,18 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
   + `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`
@@ -977,15 +1225,15 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 ## 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`
@@ -1002,12 +1250,12 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 
 ## 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`
@@ -1042,10 +1290,10 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -1053,7 +1301,7 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -1094,7 +1342,7 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 
 ## 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
@@ -1107,7 +1355,7 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
     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
 
@@ -1128,20 +1376,20 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
     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`
@@ -1160,15 +1408,15 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
     ---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`
@@ -1178,14 +1426,14 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -1195,14 +1443,14 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 ## 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`
@@ -1216,47 +1464,47 @@ server will generate `doc.json` and `doc.md` in `LOGPATH`.
 * `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`
@@ -1303,13 +1551,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`
@@ -1336,21 +1584,21 @@ f( -- view comments of `1` and `2` in completion
 * `CHG` `Lua.workspace.library`: use `path[]` instead of `<path, true>`
 * `FIX` missed syntax error `local a <const>= 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<integer, V>` 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`
@@ -1361,9 +1609,9 @@ f( -- view comments of `1` and `2` in completion
 * `CHG` intelli-scense: improve infer across `table<K, V>` 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`
@@ -1372,69 +1620,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`
@@ -1444,8 +1692,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`
@@ -1467,15 +1715,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<string, number>`
-* `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<string, number>`
+* `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`
@@ -1500,7 +1748,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`
@@ -1524,7 +1772,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`
@@ -1533,12 +1781,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
@@ -1552,16 +1800,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 `<close>`
 * `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`
@@ -1573,16 +1821,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`
@@ -1608,12 +1856,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`
@@ -1621,12 +1869,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`
@@ -1637,7 +1885,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`
@@ -1662,9 +1910,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`
@@ -1681,7 +1929,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`
@@ -1689,7 +1937,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/doc/en-us/config.md b/doc/en-us/config.md
index 28fd118ec..9e087d70b 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.
@@ -222,7 +254,9 @@ Array<string>
 * ``"err-nonstandard-symbol"``
 * ``"err-then-as-do"``
 * ``"exp-in-action"``
+* ``"global-element"``
 * ``"global-in-nil-env"``
+* ``"incomplete-signature-doc"``
 * ``"index-in-func-name"``
 * ``"invisible"``
 * ``"jump-local-scope"``
@@ -267,11 +301,16 @@ Array<string>
 * ``"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"``
@@ -410,10 +449,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -431,6 +475,9 @@ object<string, string>
     * 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
@@ -465,6 +512,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -529,10 +577,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -550,6 +603,9 @@ object<string, string>
     * 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
@@ -584,6 +640,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -758,10 +815,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Any",
@@ -769,6 +834,15 @@ object<string, string>
     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.
     */
@@ -782,6 +856,10 @@ object<string, string>
     */
     "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",
@@ -1006,10 +1084,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Warning",
@@ -1017,6 +1103,15 @@ object<string, string>
     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.
     */
@@ -1030,6 +1125,10 @@ object<string, string>
     */
     "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",
@@ -1561,7 +1660,7 @@ string
 
 # misc.parameters
 
-[Command line parameters](https://github.com/sumneko/lua-telemetry-server/tree/master/method) when starting the language server in VSCode.
+[Command line parameters](https://github.com/LuaLS/lua-telemetry-server/tree/master/method) when starting the language server in VSCode.
 
 ## type
 
@@ -1575,6 +1674,22 @@ Array<string>
 []
 ```
 
+# nameStyle.config
+
+Set name style config
+
+## type
+
+```ts
+Object<string, string | array>
+```
+
+## 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.
@@ -1609,6 +1724,8 @@ object<string, string>
     "ffi": "default",
     "io": "default",
     "jit": "default",
+    "jit.profile": "default",
+    "jit.util": "default",
     "math": "default",
     "os": "default",
     "package": "default",
@@ -1736,7 +1853,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 the [wiki](https://luals.github.io/wiki/plugins) to learn more.
 
 ## type
 
@@ -1925,23 +2042,6 @@ Array<string>
 []
 ```
 
-# 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.
@@ -2161,22 +2261,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-Provide language server for the Lua files of the following scheme.
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 Ignore files list in `.gitignore` .
@@ -2195,7 +2279,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
 
@@ -2207,4 +2291,4 @@ Array<string>
 
 ```jsonc
 []
-```
\ No newline at end of file
+```
diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md
index aa9bd81e4..68643bddd 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.
@@ -222,7 +254,9 @@ Array<string>
 * ``"err-nonstandard-symbol"``
 * ``"err-then-as-do"``
 * ``"exp-in-action"``
+* ``"global-element"``
 * ``"global-in-nil-env"``
+* ``"incomplete-signature-doc"``
 * ``"index-in-func-name"``
 * ``"invisible"``
 * ``"jump-local-scope"``
@@ -267,11 +301,16 @@ Array<string>
 * ``"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"``
@@ -410,10 +449,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -431,6 +475,9 @@ object<string, string>
     * 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
@@ -465,6 +512,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -529,10 +577,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -550,6 +603,9 @@ object<string, string>
     * 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
@@ -584,6 +640,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -681,7 +738,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1` 
+    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1`
     */
     "ambiguity-1": "Any",
     /*
@@ -758,10 +815,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Any",
@@ -769,6 +834,15 @@ object<string, string>
     首字母小写的全局变量定义
     */
     "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.
     */
@@ -782,6 +856,10 @@ object<string, string>
     */
     "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",
@@ -929,7 +1007,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1` 
+    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1`
     */
     "ambiguity-1": "Warning",
     /*
@@ -1006,10 +1084,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Warning",
@@ -1017,6 +1103,15 @@ object<string, string>
     首字母小写的全局变量定义
     */
     "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.
     */
@@ -1030,6 +1125,10 @@ object<string, string>
     */
     "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",
@@ -1561,7 +1660,7 @@ string
 
 # 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
 
@@ -1575,6 +1674,22 @@ Array<string>
 []
 ```
 
+# nameStyle.config
+
+Set name style config
+
+## type
+
+```ts
+Object<string, string | array>
+```
+
+## 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.
@@ -1609,6 +1724,8 @@ object<string, string>
     "ffi": "default",
     "io": "default",
     "jit": "default",
+    "jit.profile": "default",
+    "jit.util": "default",
     "math": "default",
     "os": "default",
     "package": "default",
@@ -1736,7 +1853,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 the [wiki](https://luals.github.io/wiki/plugins) to learn more.
 
 ## type
 
@@ -1925,23 +2042,6 @@ Array<string>
 []
 ```
 
-# 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.
@@ -2161,22 +2261,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-Provide language server for the Lua files of the following scheme.
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 Ignore files list in `.gitignore` .
@@ -2195,7 +2279,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
 
@@ -2207,4 +2291,4 @@ Array<string>
 
 ```jsonc
 []
-```
\ No newline at end of file
+```
diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md
index 3cde16083..bf33501d5 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` 此文件。
@@ -222,7 +254,9 @@ Array<string>
 * ``"err-nonstandard-symbol"``
 * ``"err-then-as-do"``
 * ``"exp-in-action"``
+* ``"global-element"``
 * ``"global-in-nil-env"``
+* ``"incomplete-signature-doc"``
 * ``"index-in-func-name"``
 * ``"invisible"``
 * ``"jump-local-scope"``
@@ -267,11 +301,16 @@ Array<string>
 * ``"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"``
@@ -410,10 +449,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -431,6 +475,9 @@ object<string, string>
     * 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
@@ -465,6 +512,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -529,10 +577,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -550,6 +603,9 @@ object<string, string>
     * 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
@@ -584,6 +640,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -681,7 +738,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1` 
+    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1`
     */
     "ambiguity-1": "Any",
     /*
@@ -758,10 +815,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Any",
@@ -769,6 +834,15 @@ object<string, string>
     首字母小写的全局变量定义
     */
     "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.
     */
@@ -782,6 +856,10 @@ object<string, string>
     */
     "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",
@@ -928,7 +1006,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1` 
+    优先级歧义，如：`num or 0 + 1`，推测用户的实际期望为 `(num or 0) + 1`
     */
     "ambiguity-1": "Warning",
     /*
@@ -1005,10 +1083,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Warning",
@@ -1016,6 +1102,15 @@ object<string, string>
     首字母小写的全局变量定义
     */
     "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.
     */
@@ -1029,6 +1124,10 @@ object<string, string>
     */
     "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",
@@ -1560,7 +1659,7 @@ string
 
 # misc.parameters
 
-VSCode中启动语言服务时的[命令行参数](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)。
+VSCode中启动语言服务时的[命令行参数](https://luals.github.io/wiki/usage/#arguments)。
 
 ## type
 
@@ -1574,6 +1673,22 @@ Array<string>
 []
 ```
 
+# nameStyle.config
+
+设定命名风格检查的配置
+
+## type
+
+```ts
+Object<string, string | array>
+```
+
+## default
+
+```jsonc
+{}
+```
+
 # runtime.builtin
 
 调整内置库的启用状态，你可以根据实际运行环境禁用掉不存在的库（或重新定义）。
@@ -1608,6 +1723,8 @@ object<string, string>
     "ffi": "default",
     "io": "default",
     "jit": "default",
+    "jit.profile": "default",
+    "jit.util": "default",
     "math": "default",
     "os": "default",
     "package": "default",
@@ -1735,7 +1852,7 @@ false
 
 # runtime.plugin
 
-插件路径，请查阅[文档](https://github.com/sumneko/lua-language-server/wiki/Plugins)了解用法。
+插件路径，请查阅[文档](https://luals.github.io/wiki/plugins)了解用法。
 
 ## type
 
@@ -1924,23 +2041,6 @@ Array<string>
 []
 ```
 
-# telemetry.enable
-
-启用遥测，通过网络发送你的编辑器信息与错误日志。在[此处](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)阅读我们的隐私声明。
-
-
-## type
-
-```ts
-boolean | null
-```
-
-## default
-
-```jsonc
-null
-```
-
 # type.castNumberToInteger
 
 允许将 `number` 类型赋给 `integer` 类型。
@@ -2160,22 +2260,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-为以下 scheme 的lua文件提供语言服务。
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 忽略 `.gitignore` 中列举的文件。
@@ -2194,7 +2278,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
 
@@ -2206,4 +2290,4 @@ Array<string>
 
 ```jsonc
 []
-```
\ No newline at end of file
+```
diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md
index 636cdd1ae..a39b8f7b0 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` 此檔案。
@@ -222,7 +254,9 @@ Array<string>
 * ``"err-nonstandard-symbol"``
 * ``"err-then-as-do"``
 * ``"exp-in-action"``
+* ``"global-element"``
 * ``"global-in-nil-env"``
+* ``"incomplete-signature-doc"``
 * ``"index-in-func-name"``
 * ``"invisible"``
 * ``"jump-local-scope"``
@@ -267,11 +301,16 @@ Array<string>
 * ``"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"``
@@ -410,10 +449,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -431,6 +475,9 @@ object<string, string>
     * 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
@@ -465,6 +512,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -529,10 +577,15 @@ object<string, string>
     "await": "Fallback",
     /*
     * codestyle-check
+    * name-style-check
     * spell-check
     */
     "codestyle": "Fallback",
     /*
+    * global-element
+    */
+    "conventions": "Fallback",
+    /*
     * duplicate-index
     * duplicate-set-field
     */
@@ -550,6 +603,9 @@ object<string, string>
     * 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
@@ -584,6 +640,7 @@ object<string, string>
     */
     "type-check": "Fallback",
     /*
+    * missing-fields
     * missing-parameter
     * missing-return
     * missing-return-value
@@ -758,10 +815,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Any",
@@ -769,6 +834,15 @@ object<string, string>
     首字母小寫的全域變數定義
     */
     "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.
     */
@@ -782,6 +856,10 @@ object<string, string>
     */
     "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",
@@ -1005,10 +1083,18 @@ object<string, string>
     */
     "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",
+    /*
     Enable diagnostics for accesses to fields which are invisible.
     */
     "invisible": "Warning",
@@ -1016,6 +1102,15 @@ object<string, string>
     首字母小寫的全域變數定義
     */
     "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.
     */
@@ -1029,6 +1124,10 @@ object<string, string>
     */
     "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",
@@ -1560,7 +1659,7 @@ string
 
 # misc.parameters
 
-VSCode中啟動語言伺服時的[命令列參數](https://github.com/sumneko/lua-language-server/wiki/Getting-Started#arguments)。
+VSCode中啟動語言伺服時的[命令列參數](https://luals.github.io/wiki/usage#arguments)。
 
 ## type
 
@@ -1574,6 +1673,22 @@ Array<string>
 []
 ```
 
+# nameStyle.config
+
+Set name style config
+
+## type
+
+```ts
+Object<string, string | array>
+```
+
+## default
+
+```jsonc
+{}
+```
+
 # runtime.builtin
 
 調整內建庫的啟用狀態，你可以根據實際執行環境停用（或重新定義）不存在的庫。
@@ -1608,6 +1723,8 @@ object<string, string>
     "ffi": "default",
     "io": "default",
     "jit": "default",
+    "jit.profile": "default",
+    "jit.util": "default",
     "math": "default",
     "os": "default",
     "package": "default",
@@ -1735,7 +1852,7 @@ false
 
 # runtime.plugin
 
-延伸模組路徑，請查閱[文件](https://github.com/sumneko/lua-language-server/wiki/Plugins)瞭解用法。
+延伸模組路徑，請查閱[文件](https://luals.github.io/wiki/plugins)瞭解用法。
 
 ## type
 
@@ -1924,23 +2041,6 @@ Array<string>
 []
 ```
 
-# telemetry.enable
-
-啟用遙測，透過網路發送你的編輯器資訊與錯誤日誌。在[此處](https://github.com/sumneko/lua-language-server/wiki/Home#privacy)閱讀我們的隱私聲明。
-
-
-## type
-
-```ts
-boolean | null
-```
-
-## default
-
-```jsonc
-null
-```
-
 # type.castNumberToInteger
 
 允許將 `number` 類型賦值給 `integer` 類型。
@@ -2160,22 +2260,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-為以下 `scheme` 的lua檔案提供語言伺服。
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 忽略 `.gitignore` 中列舉的檔案。
@@ -2194,7 +2278,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
 
@@ -2206,4 +2290,4 @@ Array<string>
 
 ```jsonc
 []
-```
\ No newline at end of file
+```
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 fb203d7e9..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                  =
@@ -150,6 +166,16 @@ 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 ...'
@@ -177,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:{}'
@@ -275,10 +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   = -- TODO: need translate!
+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         = -- TODO: need translate!
+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           =
 '<class name> expected.'
 PARSER_LUADOC_MISS_EXTENDS_SYMBOL       =
@@ -424,8 +454,10 @@ ACTION_MARK_ASYNC       =
 'Mark current function as async.'
 ACTION_ADD_DICT         =
 'Add \'{}\' to workspace dict'
-ACTION_FIX_ADD_PAREN    = -- TODO: need translate!
+ACTION_FIX_ADD_PAREN    =
 'Add parentheses.'
+ACTION_AUTOREQUIRE      =
+"Import '{}' as {}"
 
 COMMAND_DISABLE_DIAG       =
 'Disable diagnostics'
@@ -447,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 {}'
@@ -524,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         =
@@ -564,6 +598,22 @@ 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             =
 [[
@@ -598,6 +648,14 @@ 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}`'
@@ -645,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 =
 [=[
@@ -696,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 =
 [=[
@@ -746,7 +804,7 @@ local enums = {
 ---| `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 =
 [=[
@@ -771,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 =
 [=[
@@ -809,7 +867,7 @@ 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 =
 [=[
@@ -840,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 =
 [=[
@@ -897,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 =
 [=[
@@ -916,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 =
 [=[
@@ -931,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 =
 [=[
@@ -942,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 =
 [=[
@@ -957,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 =
 [=[
@@ -982,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 =
 [=[
@@ -992,7 +1050,7 @@ Define something that can be viewed for more information
 `---@see <text>`
 
 ---
-[View Wiki](https://github.com/sumneko/lua-language-server/wiki/Annotations#see)
+[View Wiki](https://luals.github.io/wiki/annotations#see)
 ]=]
 LUADOC_DESC_DIAGNOSTIC =
 [=[
@@ -1000,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 <action>[: <name>]`
@@ -1018,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 =
 [=[
@@ -1035,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 =
 [=[
@@ -1045,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 =
 [=[
@@ -1057,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 =
 [=[
@@ -1092,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 =
 [=[
@@ -1122,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
@@ -1151,6 +1209,33 @@ 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 <path>`
+
+## 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
@@ -1175,7 +1260,7 @@ 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 accessable from child classes.
+from within their class and are not accessible from child classes.
 
 ## Syntax
 `@private`
diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua
index 507893bd3..80926d12d 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       =
@@ -133,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                 =
@@ -256,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              =
@@ -265,22 +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 server in VSCode.'
+'[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.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.'
 config.type.castNumberToInteger          =
 'Allowed to assign the `number` type to the `integer` type.'
 config.type.weakUnionCheck               =
@@ -369,8 +367,14 @@ 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']        =
@@ -393,6 +397,8 @@ 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']   =
@@ -411,6 +417,8 @@ 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  =
@@ -419,3 +427,10 @@ 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..b14b260e1 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.mixinteger['>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 aabb3a197..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}`.'
@@ -150,6 +166,16 @@ 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 ...'
@@ -177,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: {}'
@@ -279,6 +305,10 @@ 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 <class name>.'
 PARSER_LUADOC_MISS_EXTENDS_SYMBOL       =
@@ -426,6 +456,8 @@ 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.'
@@ -447,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 {}.'
@@ -524,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         =
@@ -564,6 +598,22 @@ 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             =
 [[
@@ -598,6 +648,14 @@ 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}`'
@@ -645,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!
 [=[
@@ -696,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!
 [=[
@@ -746,7 +804,7 @@ local enums = {
 ---| `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!
 [=[
@@ -771,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!
 [=[
@@ -809,7 +867,7 @@ 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!
 [=[
@@ -840,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!
 [=[
@@ -897,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!
 [=[
@@ -916,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!
 [=[
@@ -931,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!
 [=[
@@ -942,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!
 [=[
@@ -957,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!
 [=[
@@ -982,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!
 [=[
@@ -992,7 +1050,7 @@ Define something that can be viewed for more information
 `---@see <text>`
 
 ---
-[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!
 [=[
@@ -1000,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 <action>[: <name>]`
@@ -1018,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!
 [=[
@@ -1035,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!
 [=[
@@ -1045,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!
 [=[
@@ -1057,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!
 [=[
@@ -1092,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!
 [=[
@@ -1122,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
@@ -1151,6 +1209,33 @@ 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 <path>`
+
+## 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
diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua
index efc936e2d..4e23e0ff7 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!
@@ -133,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!
@@ -256,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!
@@ -265,22 +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.'
+'[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.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.'
 config.type.castNumberToInteger          = -- TODO: need translate!
 'Allowed to assign the `number` type to the `integer` type.'
 config.type.weakUnionCheck               = -- TODO: need translate!
@@ -369,8 +367,14 @@ 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!
@@ -393,6 +397,8 @@ 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!
@@ -411,6 +417,8 @@ 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!
@@ -419,3 +427,10 @@ 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 6adaa17db..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                  =
@@ -150,6 +166,16 @@ 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         =
 '{} 目前还不支持多工作目录，我可能需要重启才能支持新的工作目录...'
@@ -177,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            =
 '语法解析崩溃了！遗言：{}'
@@ -279,6 +305,10 @@ 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       =
@@ -426,6 +456,8 @@ ACTION_ADD_DICT         =
 '将 \'{}\' 添加到工作区的词典中。'
 ACTION_FIX_ADD_PAREN    =
 '添加括号。'
+ACTION_AUTOREQUIRE      =
+"导入 '{}' 作为 `{}`"
 
 COMMAND_DISABLE_DIAG       =
 '禁用诊断'
@@ -445,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           =
 '从 {} 中导入'
@@ -524,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         =
@@ -534,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        =
@@ -564,6 +598,22 @@ CONFIG_TYPE_ERROR                =
 '设置文件必须是lua或json格式：{}'
 CONFIG_MODIFY_FAIL_SYNTAX_ERROR  =
 '修改设置失败，设置文件中有语法错误：{}'
+CONFIG_MODIFY_FAIL_NO_WORKSPACE  =
+[[
+修改设置失败：
+* 当前模式为单文件模式，服务器只能在工作区中创建 `.luarc.json` 文件。
+* 语言客户端不支持从服务器侧修改设置。
+
+请手动修改以下设置：
+{}
+]]
+CONFIG_MODIFY_FAIL               =
+[[
+修改设置失败
+
+请手动修改以下设置：
+{}
+]]
 
 PLUGIN_RUNTIME_ERROR             =
 [[
@@ -598,6 +648,14 @@ CLI_CHECK_SUCCESS =
 '诊断完成，没有发现问题'
 CLI_CHECK_RESULTS =
 '诊断完成，共有 {} 个问题，请查看 {}'
+CLI_DOC_INITING   =
+'加载文档 ...'
+CLI_DOC_DONE      =
+[[
+文档导出完成！
+原始数据: {}
+Markdown(演示用): {}
+]]
 
 TYPE_ERROR_ENUM_GLOBAL_DISMATCH =
 '类型 `{child}` 无法匹配 `{parent}` 的枚举类型'
@@ -645,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!
 [=[
@@ -696,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!
 [=[
@@ -746,7 +804,7 @@ local enums = {
 ---| `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!
 [=[
@@ -771,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!
 [=[
@@ -809,7 +867,7 @@ 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!
 [=[
@@ -840,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!
 [=[
@@ -897,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!
 [=[
@@ -916,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!
 [=[
@@ -931,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!
 [=[
@@ -942,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!
 [=[
@@ -957,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!
 [=[
@@ -982,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!
 [=[
@@ -992,7 +1050,7 @@ Define something that can be viewed for more information
 `---@see <text>`
 
 ---
-[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!
 [=[
@@ -1000,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 <action>[: <name>]`
@@ -1018,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!
 [=[
@@ -1035,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!
 [=[
@@ -1045,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!
 [=[
@@ -1057,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!
 [=[
@@ -1092,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!
 [=[
@@ -1122,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
@@ -1151,6 +1209,33 @@ 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 <path>`
+
+## 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
diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua
index c1dde6fb5..ff623d3a6 100644
--- a/locale/zh-cn/setting.lua
+++ b/locale/zh-cn/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       =
@@ -132,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                 =
@@ -255,6 +257,8 @@ config.hint.semicolon.SameLine           =
 '2个语句在同一行时，在它们之间显示分号。'
 config.hint.semicolon.Disable            =
 '禁用虚拟分号。'
+config.codeLens.enable                   = -- TODO: need translate!
+'启用代码度量。'
 config.format.enable                     =
 '启用代码格式化程序。'
 config.format.defaultConfig              =
@@ -264,22 +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)。'
+'VSCode中启动语言服务时的[命令行参数](https://luals.github.io/wiki/usage#arguments)。'
 config.misc.executablePath               =
 'VSCode中指定可执行文件路径。'
-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)了解用法。'
 config.type.castNumberToInteger          =
 '允许将 `number` 类型赋给 `integer` 类型。'
 config.type.weakUnionCheck               =
@@ -368,8 +366,14 @@ 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!
@@ -392,6 +396,8 @@ 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!
@@ -410,6 +416,8 @@ 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!
@@ -418,3 +426,10 @@ 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 =
+'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 ec69bea86..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                  =
@@ -150,6 +166,16 @@ 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         =
 '{} 目前還不支援多工作目錄，我可能需要重新啟動才能支援新的工作目錄...'
@@ -177,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            =
 '語法解析崩潰了！遺言：{}'
@@ -279,6 +305,10 @@ 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       =
@@ -426,6 +456,8 @@ ACTION_ADD_DICT         =
 '添加 \'{}\' 到工作區字典'
 ACTION_FIX_ADD_PAREN    = -- TODO: need translate!
 '添加括号。'
+ACTION_AUTOREQUIRE      = -- TODO: need translate!
+"Import '{}' as {}"
 
 COMMAND_DISABLE_DIAG       =
 '停用診斷'
@@ -447,6 +479,8 @@ COMMAND_JSON_TO_LUA_FAILED =
 'JSON 轉 Lua 失敗：{}'
 COMMAND_ADD_DICT           =
 '添加單字到字典裡'
+COMMAND_REFERENCE_COUNT    = -- TODO: need translate!
+'{} references'
 
 COMPLETION_IMPORT_FROM           =
 '從 {} 中匯入'
@@ -524,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         =
@@ -564,6 +598,22 @@ 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             =
 [[
@@ -598,6 +648,14 @@ 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}`'
@@ -645,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 =
 [=[
@@ -696,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 =
 [=[
@@ -746,7 +804,7 @@ local enums = {
 ---| `enums.CLOSE`
 ```
 ---
-[檢視文件](https://github.com/sumneko/lua-language-server/wiki/Annotations#alias)
+[檢視文件](https://luals.github.io/wiki/annotations#alias)
 ]=]
 LUADOC_DESC_PARAM =
 [=[
@@ -771,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 =
 [=[
@@ -809,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 =
 [=[
@@ -838,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 =
 [=[
@@ -894,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 =
 [=[
@@ -912,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 =
 [=[
@@ -927,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 =
 [=[
@@ -937,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 =
 [=[
@@ -952,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 =
 [=[
@@ -977,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 =
 [=[
@@ -987,7 +1045,7 @@ LUADOC_DESC_SEE =
 `---@see <text>`
 
 ---
-[檢視文件](https://github.com/sumneko/lua-language-server/wiki/Annotations#see)
+[檢視文件](https://luals.github.io/wiki/annotations#see)
 ]=]
 LUADOC_DESC_DIAGNOSTIC =
 [=[
@@ -995,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 <action>[: <name>]`
@@ -1013,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 =
 [=[
@@ -1030,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 =
 [=[
@@ -1040,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 =
 [=[
@@ -1051,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 =
 [=[
@@ -1086,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!
 [=[
@@ -1116,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
@@ -1145,6 +1203,33 @@ 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 <path>`
+
+## 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
diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua
index b33ed7820..c8e0dbfc2 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       =
@@ -132,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                 =
@@ -255,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              =
@@ -264,22 +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)。'
+'VSCode中啟動語言伺服時的[命令列參數](https://luals.github.io/wiki/usage#arguments)。'
 config.misc.executablePath               = -- TODO: need translate!
 'Specify the executable path in VSCode.'
-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)瞭解用法。'
 config.type.castNumberToInteger          =
 '允許將 `number` 類型賦值給 `integer` 類型。'
 config.type.weakUnionCheck               =
@@ -368,8 +366,14 @@ 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!
@@ -392,6 +396,8 @@ 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!
@@ -410,6 +416,8 @@ 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!
@@ -418,3 +426,10 @@ 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 b724264b4..8ecfd472f 100644
--- a/main.lua
+++ b/main.lua
@@ -36,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
@@ -70,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 5a65e1e7d..8fba9ac95 100644
--- a/make.lua
+++ b/make.lua
@@ -5,8 +5,16 @@ 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
 
@@ -62,19 +70,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" {
+    input = lm.bindir .. "/lua-language-server" .. exe,
+    output = "bin/lua-language-server" .. exe,
+}
+
 lm:copy "copy_bootstrap" {
     input = "make/bootstrap.lua",
-    output = lm.bindir .. "/main.lua",
+    output = "bin/main.lua",
 }
 
 lm:msvc_copydll 'copy_vcrt' {
     type = "vcrt",
-    output = lm.bindir,
+    output = "bin",
 }
 
 lm:phony "all" {
     deps = {
         "lua-language-server",
+        "copy_lua-language-server",
         "copy_bootstrap",
     },
     windows = {
@@ -91,26 +108,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" {
+    lm.bindir .. "/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" {
+    "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" },
+    input = "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" },
+    input = "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 387221b37..f884c9af2 100644
--- a/make/code_format.lua
+++ b/make/code_format.lua
@@ -6,7 +6,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,33 +16,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/TypeFormat/*.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/modules.cpp b/make/modules.cpp
index c4f06fcb2..7d9eab396 100644
--- a/make/modules.cpp
+++ b/make/modules.cpp
@@ -1,4 +1,4 @@
-#include <bee/lua/binding.h>
+#include <binding/binding.h>
 
 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/OpenResty/library/prometheus.lua b/meta/3rd/OpenResty/library/prometheus.lua
index c56ebb031..88036b816 100644
--- a/meta/3rd/OpenResty/library/prometheus.lua
+++ b/meta/3rd/OpenResty/library/prometheus.lua
@@ -234,7 +234,7 @@ local PrometheusHist = {}
 ---  metric_response_sizes:observe(tonumber(ngx.var.bytes_sent))
 ---}
 ---```
----@param value string is a value that should be recorded. Required.
+---@param value number is a value that should be recorded. Required.
 ---@param label_values? string[] is an array of label values.
 function PrometheusHist:observe(value, label_values) end
 
diff --git a/meta/3rd/OpenResty/library/resty/signal.lua b/meta/3rd/OpenResty/library/resty/signal.lua
index 912b2f00e..a7e42d42b 100644
--- a/meta/3rd/OpenResty/library/resty/signal.lua
+++ b/meta/3rd/OpenResty/library/resty/signal.lua
@@ -4,40 +4,47 @@ local signal = {
     version = 0.03,
 }
 
+
 ---@alias resty.signal.name
----| '"HUP"'
----| '"INT"'
----| '"QUIT"'
----| '"ILL"'
----| '"TRAP"'
----| '"ABRT"'
----| '"BUS"'
----| '"FPE"'
----| '"KILL"'
----| '"USR1"'
----| '"SEGV"'
----| '"USR2"'
----| '"PIPE"'
----| '"ALRM"'
----| '"TERM"'
----| '"CHLD"'
----| '"CONT"'
----| '"STOP"'
----| '"TSTP"'
----| '"TTIN"'
----| '"TTOU"'
----| '"URG"'
----| '"XCPU"'
----| '"XFSZ"'
----| '"VTALRM"'
----| '"PROF"'
----| '"WINCH"'
----| '"IO"'
----| '"PWR"'
----| '"EMT"'
----| '"SYS"'
----| '"INFO"'
----| '"NONE"' # The special signal name NONE is also supported, which is mapped to zero (0).
+---| "NONE"   # SIG_NONE
+---| "HUP"    # SIG_HUP
+---| "INT"    # SIG_INT
+---| "QUIT"   # SIG_QUIT
+---| "ILL"    # SIG_ILL
+---| "TRAP"   # SIG_TRAP
+---| "ABRT"   # SIG_ABRT
+---| "BUS"    # SIG_BUS
+---| "FPE"    # SIG_FPE
+---| "KILL"   # SIG_KILL
+---| "USR1"   # SIG_USR1
+---| "SEGV"   # SIG_SEGV
+---| "USR2"   # SIG_USR2
+---| "PIPE"   # SIG_PIPE
+---| "ALRM"   # SIG_ALRM
+---| "TERM"   # SIG_TERM
+---| "CHLD"   # SIG_CHLD
+---| "CONT"   # SIG_CONT
+---| "STOP"   # SIG_STOP
+---| "TSTP"   # SIG_TSTP
+---| "TTIN"   # SIG_TTIN
+---| "TTOU"   # SIG_TTOU
+---| "URG"    # SIG_URG
+---| "XCPU"   # SIG_XCPU
+---| "XFSZ"   # SIG_XFSZ
+---| "VTALRM" # SIG_VTALRM
+---| "PROF"   # SIG_PROF
+---| "WINCH"  # SIG_WINCH
+---| "IO"     # SIG_IO
+---| "PWR"    # SIG_PWR
+---| "EMT"    # SIG_EMT
+---| "SYS"    # SIG_SYS
+---| "INFO"   # SIG_INFO
+
+
+---@alias resty.signal.signal
+---| resty.signal.name
+---| integer
+---| string
 
 
 ---
@@ -48,18 +55,20 @@ local signal = {
 -- Signal numbers are also supported when specifying nonportable system-specific signals is desired.
 --
 ---@param pid number
----@param signal_name_or_num number|resty.signal.name
+---@param signal_name_or_num resty.signal.signal
 ---
 ---@return boolean ok
 ---@return string? error
 function signal.kill(pid, signal_name_or_num) end
 
+
 ---
 -- Maps the signal name specified to the system-specific signal number.
 -- Returns `nil` if the signal name is not known.
 --
----@param name resty.signal.name
----@return number|nil
+---@param name string|resty.signal.name
+---@return integer|nil
 function signal.signum(name) end
 
+
 return signal
\ No newline at end of file
diff --git a/meta/3rd/bee/config.json b/meta/3rd/bee/config.json
new file mode 100644
index 000000000..0dce58173
--- /dev/null
+++ b/meta/3rd/bee/config.json
@@ -0,0 +1,4 @@
+{
+    "name" : "bee",
+    "words" : [ "require[%s%(\"']+bee%.%w+[%)\"']" ]
+}
diff --git a/meta/3rd/bee/library/bee/filesystem.lua b/meta/3rd/bee/library/bee/filesystem.lua
new file mode 100644
index 000000000..7cd4dcbb9
--- /dev/null
+++ b/meta/3rd/bee/library/bee/filesystem.lua
@@ -0,0 +1,240 @@
+---@meta
+
+local m = {}
+
+---@enum bee.filesystem.copy_options
+local copy_options = {
+    none = 0,
+    skip_existing = 1,
+    overwrite_existing = 2,
+    update_existing = 4,
+    recursive = 8,
+    copy_symlinks = 16,
+    skip_symlinks = 32,
+    directories_only = 64,
+    create_symlinks = 128,
+    create_hard_links = 256,
+    __in_recursive_copy = 512,
+}
+
+---@alias bee.filesystem.path_arg string|bee.filesystem.path
+
+---@class bee.filesystem.path
+---@operator div(any):bee.filesystem.path
+---@operator concat(any):bee.filesystem.path
+local path = {}
+
+---@return string
+function path:string()
+end
+
+---@return bee.filesystem.path
+function path:filename()
+end
+
+---@return bee.filesystem.path
+function path:parent_path()
+end
+
+---@return bee.filesystem.path
+function path:stem()
+end
+
+---@return bee.filesystem.path
+function path:extension()
+end
+
+---@return boolean
+function path:is_absolute()
+end
+
+---@return boolean
+function path:is_relative()
+end
+
+---@return bee.filesystem.path
+function path:remove_filename()
+end
+
+---@param filename bee.filesystem.path_arg
+---@return bee.filesystem.path
+function path:replace_filename(filename)
+end
+
+---@param extension bee.filesystem.path_arg
+---@return bee.filesystem.path
+function path:replace_extension(extension)
+end
+
+---@param path bee.filesystem.path_arg
+---@return boolean
+function path:equal_extension(path)
+end
+
+---@return bee.filesystem.path
+function path:lexically_normal()
+end
+
+---@class bee.filesystem.file_status
+local file_status = {}
+
+---@alias bee.filesystem.file_type
+---| "'none'"
+---| "'not_found'"
+---| "'regular'"
+---| "'directory'"
+---| "'symlink'"
+---| "'block'"
+---| "'character'"
+---| "'fifo'"
+---| "'socket'"
+---| "'junction'"
+---| "'unknown'"
+
+---@return bee.filesystem.file_type
+function file_status:type()
+end
+
+---@return boolean
+function file_status:exists()
+end
+
+---@return boolean
+function file_status:is_directory()
+end
+
+---@return boolean
+function file_status:is_regular_file()
+end
+
+---@class bee.filesystem.directory_entry
+local directory_entry = {}
+
+---@return bee.filesystem.path
+function directory_entry:path()
+end
+
+---@return bee.filesystem.file_status
+function directory_entry:status()
+end
+
+---@return bee.filesystem.file_status
+function directory_entry:symlink_status()
+end
+
+---@return bee.filesystem.file_type
+function directory_entry:type()
+end
+
+---@return boolean
+function directory_entry:exists()
+end
+
+---@return boolean
+function directory_entry:is_directory()
+end
+
+---@return boolean
+function directory_entry:is_regular_file()
+end
+
+---@return bee.filesystem.path
+function m.path(path)
+end
+
+---@return bee.filesystem.file_status
+function m.status(path)
+end
+
+---@return bee.filesystem.file_status
+function m.symlink_status(path)
+end
+
+---@return boolean
+function m.exists(path)
+end
+
+---@return boolean
+function m.is_directory(path)
+end
+
+---@return boolean
+function m.is_regular_file(path)
+end
+
+function m.create_directory(path)
+end
+
+function m.create_directories(path)
+end
+
+function m.rename(from, to)
+end
+
+function m.remove(path)
+end
+
+function m.remove_all(path)
+end
+
+---@return bee.filesystem.path
+function m.current_path()
+end
+
+---@param options bee.filesystem.copy_options
+function m.copy(from, to, options)
+end
+
+---@param options bee.filesystem.copy_options
+function m.copy_file(from, to, options)
+end
+
+function m.absolute(path)
+end
+
+function m.canonical(path)
+end
+
+function m.relative(path)
+end
+
+function m.last_write_time(path)
+end
+
+function m.permissions(path)
+end
+
+function m.create_symlink(target, link)
+end
+
+function m.create_directory_symlink(target, link)
+end
+
+function m.create_hard_link(target, link)
+end
+
+---@return bee.filesystem.path
+function m.temp_directory_path()
+end
+
+---@return fun(table: bee.filesystem.path[]): bee.filesystem.path
+function m.pairs(path)
+end
+
+---@return bee.filesystem.path
+function m.exe_path()
+end
+
+---@return bee.filesystem.path
+function m.dll_path()
+end
+
+---@return bee.file
+function m.filelock(path)
+end
+
+---@return bee.filesystem.path
+function m.fullpath(path)
+end
+
+return m
diff --git a/meta/3rd/bee/library/bee/platform.lua b/meta/3rd/bee/library/bee/platform.lua
new file mode 100644
index 000000000..f380a9bf4
--- /dev/null
+++ b/meta/3rd/bee/library/bee/platform.lua
@@ -0,0 +1,62 @@
+---@meta
+
+---@alias bee.platform.os
+---| '"windows"'
+---| '"android"'
+---| '"linux"'
+---| '"netbsd"'
+---| '"freebsd"'
+---| '"openbsd"'
+---| '"ios"'
+---| '"macos"'
+---| '"unknown"'
+
+---@alias bee.platform.OS
+---| '"Windows"'
+---| '"Android"'
+---| '"Linux"'
+---| '"NetBSD"'
+---| '"FreeBSD"'
+---| '"OpenBSD"'
+---| '"iOS"'
+---| '"macOS"'
+---| '"unknown"'
+
+---@alias bee.platform.arch
+---| '"x86"'
+---| '"x86_64"'
+---| '"arm"'
+---| '"arm64"'
+---| '"riscv"'
+---| '"unknown"'
+
+---@alias bee.platform.compiler
+---| '"clang"'
+---| '"gcc"'
+---| '"msvc"'
+---| '"unknown"'
+
+---@alias bee.platform.crt
+---| '"msvc"'
+---| '"bionic"'
+---| '"libstdc++"'
+---| '"libc++"'
+---| '"unknown"'
+
+local m = {
+    ---@type bee.platform.os
+    os = "unknown",
+    ---@type bee.platform.OS
+    OS = "unknown",
+    ---@type bee.platform.compiler
+    Compiler = "clang",
+    CompilerVersion = "",
+    CRTVersion = "",
+    ---@type bee.platform.crt
+    CRT = "msvc",
+    ---@type bee.platform.arch
+    Arch = "x86",
+    DEBUG = false,
+}
+
+return m
diff --git a/meta/3rd/bee/library/bee/subprocess.lua b/meta/3rd/bee/library/bee/subprocess.lua
new file mode 100644
index 000000000..0c979729e
--- /dev/null
+++ b/meta/3rd/bee/library/bee/subprocess.lua
@@ -0,0 +1,99 @@
+---@meta
+
+---@class bee.file:lightuserdata
+local file = {}
+
+---@alias bee.file.readmode integer|"'a'"
+
+---@param mode bee.file.readmode
+---@return string
+function file:read(mode)
+end
+
+---@param buf number|integer|string
+function file:write(buf)
+end
+
+---@return fun():string
+function file:lines()
+end
+
+function file:flush()
+end
+
+function file:close()
+end
+
+---@param mode "'no'"|"'full'"|"'line'"
+function file:setvbuf(mode)
+end
+
+---@class bee.process
+---@field stderr bee.file?
+---@field stdout bee.file?
+---@field stdin bee.file?
+local process = {}
+
+---@return integer exit_code
+function process:wait()
+end
+
+---@return boolean success
+function process:kill()
+end
+
+---@return integer process_id
+function process:get_id()
+end
+
+---@return boolean is_running
+function process:is_running()
+end
+
+---@return boolean success
+function process:resume()
+end
+
+---@return any native_handle
+function process:native_handle()
+end
+
+---@class bee.subprocess
+local m = {}
+
+---@alias bee.bee.subprocess.spawn_io_arg boolean|file*|bee.file|"'stderr'"|"'stdout'"
+
+---@class bee.subprocess.spawn.options : string[]
+---@field stdin bee.bee.subprocess.spawn_io_arg?
+---@field stdout bee.bee.subprocess.spawn_io_arg?
+---@field stderr bee.bee.subprocess.spawn_io_arg?
+---@field env table<string,string>?
+---@field suspended boolean?
+---@field detached boolean?
+
+---@param options bee.subprocess.spawn.options
+---@return bee.process process
+function m.spawn(options)
+end
+
+---@param file file*|bee.file
+---@return integer offset
+---@return string? error_message
+function m.peek(file)
+end
+
+function m.filemode()
+end
+
+---@param name string
+---@param value string
+---@return boolean success
+---@return string? error_message
+function m.setenv(name, value)
+end
+
+---@return integer current_process_id
+function m.get_id()
+end
+
+return m
diff --git a/meta/3rd/bee/library/bee/thread.lua b/meta/3rd/bee/library/bee/thread.lua
new file mode 100644
index 000000000..9e005ea45
--- /dev/null
+++ b/meta/3rd/bee/library/bee/thread.lua
@@ -0,0 +1,62 @@
+---@meta
+
+---@class bee.rpc:lightuserdata
+
+---@class bee.thread_obj:lightuserdata
+
+---@class bee.channel
+local channel = {}
+
+function channel:push(...)
+end
+
+---@return boolean ok
+---@return ...
+function channel:pop()
+end
+
+---@return ...
+function channel:bpop()
+end
+
+---@class bee.thread
+local m = {}
+
+function m.sleep(sec)
+end
+
+---@return bee.thread_obj
+function m.thread(source, params)
+end
+
+function m.newchannel(name)
+end
+
+---@return bee.channel
+function m.channel(name)
+end
+
+function m.reset()
+end
+
+---@param th bee.thread_obj
+function m.wait(th)
+end
+
+function m.setname(name)
+end
+
+---@return bee.rpc
+function m.rpc_create()
+end
+
+function m.rpc_wait(rpc)
+end
+
+function m.rpc_return(rpc)
+end
+
+function m.preload_module()
+end
+
+return m
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/config.json b/meta/3rd/ffi-reflect/config.json
new file mode 100644
index 000000000..544b7b4dd
--- /dev/null
+++ b/meta/3rd/ffi-reflect/config.json
@@ -0,0 +1,3 @@
+{
+    
+}
\ No newline at end of file
diff --git a/meta/3rd/ffi-reflect/library/reflect.lua b/meta/3rd/ffi-reflect/library/reflect.lua
new file mode 100644
index 000000000..8a9b759c2
--- /dev/null
+++ b/meta/3rd/ffi-reflect/library/reflect.lua
@@ -0,0 +1,128 @@
+---@meta
+
+---@alias ffi.typeinfo.what "int"
+---|"void"
+---|"float"
+---|"enum"
+---|"constant"
+---|"ptr"
+---|"ref"
+---|"array"
+---|"struct"
+---|"union"
+---|"func"
+---|"field"
+---|"bitfield"
+---|"typedef"
+
+---@class ffi.typeinfo
+---@field what ffi.typeinfo.what
+
+---@class ffi.enuminfo : ffi.typeinfo
+---@field name string?
+---@field size number|string
+---@field alignment number
+---@field type ffi.typeinfo
+local enuminfo = {}
+
+---@return ffi.constantinfo[]
+function enuminfo:values()
+end
+
+---@return ffi.constantinfo
+function enuminfo:value(name_or_index)
+end
+
+---@class ffi.funcinfo : ffi.typeinfo
+---@field name string?
+---@field sym_name string?
+---@field return_type ffi.funcinfo
+---@field nargs integer
+---@field vararg boolean
+---@field sse_reg_params boolean
+---@field convention "cdecl"|"thiscall"|"fastcall"|"stdcall"
+local funcinfo = {}
+
+---@return ffi.fieldinfo[]
+function funcinfo:arguments()
+end
+
+---@return ffi.fieldinfo
+function funcinfo:argument(name_or_index)
+end
+
+---@class ffi.unioninfo : ffi.typeinfo
+---@field name string?
+---@field size integer
+---@field alignment number
+---@field const boolean
+---@field volatile boolean
+---@field transparent boolean
+local unioninfo = {}
+
+---@return ffi.typeinfo[]
+function unioninfo:members()
+end
+
+---@return ffi.typeinfo
+function unioninfo:member(name_or_index)
+end
+
+---@class ffi.structinfo : ffi.unioninfo
+---@field vla boolean
+
+---@class ffi.floatinfo : ffi.typeinfo
+---@field size integer
+---@field alignment number
+---@field const boolean
+---@field volatile boolean
+
+---@alias ffi.voidinfo ffi.floatinfo
+
+---@class ffi.intinfo : ffi.floatinfo
+---@field bool boolean
+---@field unsigned boolean
+---@field long boolean
+
+---@class ffi.constantinfo : ffi.typeinfo
+---@field name string?
+---@field type ffi.typeinfo
+---@field value integer
+
+---@class ffi.ptrinfo : ffi.typeinfo
+---@field size integer
+---@field alignment number
+---@field const boolean
+---@field volatile boolean
+---@field element_type ffi.typeinfo
+
+---@alias ffi.refinfo ffi.ptrinfo
+
+---@class ffi.arrayinfo : ffi.ptrinfo
+---@field vla boolean
+---@field vector boolean
+---@field complex boolean
+
+---@class ffi.fieldinfo : ffi.typeinfo
+---@field name string?
+---@field offset number
+---@field type ffi.typeinfo
+
+---@class ffi.bitfield : ffi.fieldinfo
+---@field size integer|string
+
+local reflect = {}
+
+---reflection cdata c defined
+---@param v ffi.cdata*
+---@return ffi.typeinfo
+function reflect.typeof(v)
+end
+
+---try get cdata metatable
+---@param v ffi.cdata*
+---@return table
+function reflect.getmetatable(v)
+end
+
+return reflect
\ No newline at end of file
diff --git a/meta/3rd/love2d/config.json b/meta/3rd/love2d/config.json
index ccb483b30..d44784d32 100644
--- a/meta/3rd/love2d/config.json
+++ b/meta/3rd/love2d/config.json
@@ -2,6 +2,9 @@
     "name"   : "LÖVE",
     "words"  : ["love%.%w+"],
     "settings" : {
-        "Lua.runtime.version" : "LuaJIT"
+        "Lua.runtime.version" : "LuaJIT",
+        "Lua.runtime.special" : {
+            "love.filesystem.load" : "loadfile"
+        }
     }
 }
diff --git a/meta/3rd/love2d/library/love.lua b/meta/3rd/love2d/library/love.lua
index 8c2e3f182..7ca517a5e 100644
--- a/meta/3rd/love2d/library/love.lua
+++ b/meta/3rd/love2d/library/love.lua
@@ -1,13 +1,18 @@
 ---@meta
 
 -- version: 11.4
-
+---
+---[Open in Browser](https://love2d.org/wiki/love)
+---
 ---@class love
 love = {}
 
 ---
 ---Gets the current running version of LÖVE.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love.getVersion)
+---
 ---@return number major # The major version of LÖVE, i.e. 0 for version 0.9.1.
 ---@return number minor # The minor version of LÖVE, i.e. 9 for version 0.9.1.
 ---@return number revision # The revision version of LÖVE, i.e. 1 for version 0.9.1.
@@ -19,12 +24,18 @@ function love.getVersion() end
 ---
 ---When deprecation output is enabled, the first use of a formally deprecated LÖVE API will show a message at the bottom of the screen for a short time, and print the message to the console.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love.hasDeprecationOutput)
+---
 ---@return boolean enabled # Whether deprecation output is enabled.
 function love.hasDeprecationOutput() end
 
 ---
 ---Gets whether the given version is compatible with the current running version of LÖVE.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love.isVersionCompatible)
+---
 ---@overload fun(major: number, minor: number, revision: number):boolean
 ---@param version string # The version to check (for example '11.3' or '0.10.2').
 ---@return boolean compatible # Whether the given version is compatible with the current running version of LÖVE.
@@ -35,18 +46,27 @@ function love.isVersionCompatible(version) end
 ---
 ---When deprecation output is enabled, the first use of a formally deprecated LÖVE API will show a message at the bottom of the screen for a short time, and print the message to the console.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love.setDeprecationOutput)
+---
 ---@param enable boolean # Whether to enable or disable deprecation output.
 function love.setDeprecationOutput(enable) end
 
 ---
 ---The superclass of all data.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love)
+---
 ---@class love.Data: love.Object
 local Data = {}
 
 ---
 ---Creates a new copy of the Data object.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Data:clone)
+---
 ---@return love.Data clone # The new copy.
 function Data:clone() end
 
@@ -55,30 +75,45 @@ function Data:clone() end
 ---
 ---This function should be preferred instead of Data:getPointer because the latter uses light userdata which can't store more all possible memory addresses on some new ARM64 architectures, when LuaJIT is used.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Data:getFFIPointer)
+---
 ---@return ffi.cdata* pointer # A raw void* pointer to the Data, or nil if FFI is unavailable.
 function Data:getFFIPointer() end
 
 ---
 ---Gets a pointer to the Data. Can be used with libraries such as LuaJIT's FFI.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Data:getPointer)
+---
 ---@return lightuserdata pointer # A raw pointer to the Data.
 function Data:getPointer() end
 
 ---
 ---Gets the Data's size in bytes.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Data:getSize)
+---
 ---@return number size # The size of the Data in bytes.
 function Data:getSize() end
 
 ---
 ---Gets the full Data as a string.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Data:getString)
+---
 ---@return string data # The raw data.
 function Data:getString() end
 
 ---
 ---The superclass of all LÖVE types.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/love)
+---
 ---@class love.Object
 local Object = {}
 
@@ -87,18 +122,27 @@ local Object = {}
 ---
 ---This method can be used to immediately clean up resources without waiting for Lua's garbage collector.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Object:release)
+---
 ---@return boolean success # True if the object was released by this call, false if it had been previously released.
 function Object:release() end
 
 ---
 ---Gets the type of the object as a string.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Object:type)
+---
 ---@return string type # The type as a string.
 function Object:type() end
 
 ---
 ---Checks whether an object is of a certain type. If the object has the type with the specified name in its hierarchy, this function will return true.
 ---
+---
+---[Open in Browser](https://love2d.org/wiki/Object:typeOf)
+---
 ---@param name string # The name of the type to check for.
 ---@return boolean b # True if the object is of the specified type, false otherwise.
 function Object:typeOf(name) end
@@ -126,7 +170,7 @@ function Object:typeOf(name) end
 ---
 ---The error handler, used to display error messages.
 ---
----@alias love.errorhandler fun(msg: string)
+---@alias love.errorhandler fun(msg: string):function
 
 ---
 ---Callback function triggered when a file is dragged and dropped onto the window.
diff --git a/meta/3rd/love2d/library/love/event.lua b/meta/3rd/love2d/library/love/event.lua
index e06e3ae8c..0b61f2507 100644
--- a/meta/3rd/love2d/library/love/event.lua
+++ b/meta/3rd/love2d/library/love/event.lua
@@ -66,7 +66,7 @@ function love.event.push(n, a, b, c, d, e, f, ...) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/love.event.quit)
 ---
----@overload fun('restart': string|'restart')
+---@overload fun(restart: string|'restart')
 ---@param exitstatus? number # The program exit status to use when closing the application.
 function love.event.quit(exitstatus) end
 
diff --git a/meta/3rd/love2d/library/love/graphics.lua b/meta/3rd/love2d/library/love/graphics.lua
index bb37237d2..02647e0bb 100644
--- a/meta/3rd/love2d/library/love/graphics.lua
+++ b/meta/3rd/love2d/library/love/graphics.lua
@@ -410,11 +410,11 @@ function love.graphics.getMeshCullMode() end
 ---love.graphics.getDimensions gets the dimensions of the window in units scaled by the screen's DPI scale factor, rather than pixels. Use getDimensions for calculations related to drawing to the screen and using the graphics coordinate system (calculating the center of the screen, for example), and getPixelDimensions only when dealing specifically with underlying pixels (pixel-related calculations in a pixel Shader, for example).
 ---
 ---
----[Open in Browser](https://love2d.org/wiki/love.graphics.getPixelDimenions)
+---[Open in Browser](https://love2d.org/wiki/love.graphics.getPixelDimensions)
 ---
 ---@return number pixelwidth # The width of the window in pixels.
 ---@return number pixelheight # The height of the window in pixels.
-function love.graphics.getPixelDimenions() end
+function love.graphics.getPixelDimensions() end
 
 ---
 ---Gets the height in pixels of the window.
@@ -731,13 +731,13 @@ function love.graphics.newFont(filename) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/love.graphics.newImage)
 ---
----@overload fun(fileData: love.FileData, flags?: table):love.Image
----@overload fun(imageData: love.ImageData, flags?: table):love.Image
----@overload fun(compressedImageData: love.CompressedImageData, flags?: table):love.Image
+---@overload fun(fileData: love.FileData, settings?: table):love.Image
+---@overload fun(imageData: love.ImageData, settings?: table):love.Image
+---@overload fun(compressedImageData: love.CompressedImageData, settings?: table):love.Image
 ---@param filename string # The filepath to the image file.
----@param flags? {dpiscale: number, linear: boolean, mipmaps: boolean} # A table containing the following fields:
+---@param settings? {dpiscale: number, linear: boolean, mipmaps: boolean} # A table containing the following fields:
 ---@return love.Image image # A new Image object which can be drawn on screen.
-function love.graphics.newImage(filename, flags) end
+function love.graphics.newImage(filename, settings) end
 
 ---
 ---Creates a new specifically formatted image.
@@ -794,6 +794,7 @@ function love.graphics.newParticleSystem(image, buffer) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/love.graphics.newQuad)
 ---
+---@overload fun(x: number, y: number, width: number, height: number, texture: love.Texture):love.Quad
 ---@param x number # The top-left position in the Image along the x-axis.
 ---@param y number # The top-left position in the Image along the y-axis.
 ---@param width number # The width of the Quad in the Image. (Must be greater than 0.)
@@ -1074,8 +1075,7 @@ function love.graphics.scale(sx, sy) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/love.graphics.setBackgroundColor)
 ---
----@overload fun()
----@overload fun()
+---@overload fun(rgba: table)
 ---@param red number # The red component (0-1).
 ---@param green number # The green component (0-1).
 ---@param blue number # The blue component (0-1).
@@ -2243,18 +2243,19 @@ function ParticleSystem:setBufferSize(size) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/ParticleSystem:setColors)
 ---
+---@overload fun(self: love.ParticleSystem, rgba1: table, rgba2: table, rgba8: table)
 ---@param r1 number # First color, red component (0-1).
 ---@param g1 number # First color, green component (0-1).
 ---@param b1 number # First color, blue component (0-1).
----@param a1 number # First color, alpha component (0-1).
----@param r2 number # Second color, red component (0-1).
----@param g2 number # Second color, green component (0-1).
----@param b2 number # Second color, blue component (0-1).
----@param a2 number # Second color, alpha component (0-1).
----@param r8 number # Eighth color, red component (0-1).
----@param g8 number # Eighth color, green component (0-1).
----@param b8 number # Eighth color, blue component (0-1).
----@param a8 number # Eighth color, alpha component (0-1).
+---@param a1? number # First color, alpha component (0-1).
+---@param r2? number # Second color, red component (0-1).
+---@param g2? number # Second color, green component (0-1).
+---@param b2? number # Second color, blue component (0-1).
+---@param a2? number # Second color, alpha component (0-1).
+---@param r8? number # Eighth color, red component (0-1).
+---@param g8? number # Eighth color, green component (0-1).
+---@param b8? number # Eighth color, blue component (0-1).
+---@param a8? number # Eighth color, alpha component (0-1).
 function ParticleSystem:setColors(r1, g1, b1, a1, r2, g2, b2, a2, r8, g8, b8, a8) end
 
 ---
@@ -2420,8 +2421,8 @@ function ParticleSystem:setSizeVariation(variation) end
 ---[Open in Browser](https://love2d.org/wiki/ParticleSystem:setSizes)
 ---
 ---@param size1 number # The first size.
----@param size2 number # The second size.
----@param size8 number # The eighth size.
+---@param size2? number # The second size.
+---@param size8? number # The eighth size.
 function ParticleSystem:setSizes(size1, size2, size8) end
 
 ---
diff --git a/meta/3rd/love2d/library/love/math.lua b/meta/3rd/love2d/library/love/math.lua
index b0e5516e8..e8397cb50 100644
--- a/meta/3rd/love2d/library/love/math.lua
+++ b/meta/3rd/love2d/library/love/math.lua
@@ -532,6 +532,19 @@ function Transform:clone() end
 ---
 ---@return number e1_1 # The first column of the first row of the matrix.
 ---@return number e1_2 # The second column of the first row of the matrix.
+---@return number e1_3 # The third column of the first row of the matrix.
+---@return number e1_4 # The fourth column of the first row of the matrix.
+---@return number e2_1 # The first column of the second row of the matrix.
+---@return number e2_2 # The second column of the second row of the matrix.
+---@return number e2_3 # The third column of the second row of the matrix.
+---@return number e2_4 # The fourth column of the second row of the matrix.
+---@return number e3_1 # The first column of the third row of the matrix.
+---@return number e3_2 # The second column of the third row of the matrix.
+---@return number e3_3 # The third column of the third row of the matrix.
+---@return number e3_4 # The fourth column of the third row of the matrix.
+---@return number e4_1 # The first column of the fourth row of the matrix.
+---@return number e4_2 # The second column of the fourth row of the matrix.
+---@return number e4_3 # The third column of the fourth row of the matrix.
 ---@return number e4_4 # The fourth column of the fourth row of the matrix.
 function Transform:getMatrix() end
 
@@ -605,15 +618,27 @@ function Transform:scale(sx, sy) end
 ---
 ---[Open in Browser](https://love2d.org/wiki/Transform:setMatrix)
 ---
----@overload fun(self: love.Transform, layout: love.MatrixLayout, e1_1: number, e1_2: number, ..., e4_4: number):love.Transform
+---@overload fun(self: love.Transform, layout: love.MatrixLayout, e1_1: number, e1_2: number, e1_3: number, e1_4: number, e2_1: number, e2_2: number, e2_3: number, e2_4: number, e3_1: number, e3_2: number, e3_3: number, e3_4: number, e4_1: number, e4_2: number, e4_3: number, e4_4: number):love.Transform
 ---@overload fun(self: love.Transform, layout: love.MatrixLayout, matrix: table):love.Transform
 ---@overload fun(self: love.Transform, layout: love.MatrixLayout, matrix: table):love.Transform
 ---@param e1_1 number # The first column of the first row of the matrix.
 ---@param e1_2 number # The second column of the first row of the matrix.
----@vararg number # Additional matrix elements.
+---@param e1_3 number # The third column of the first row of the matrix.
+---@param e1_4 number # The fourth column of the first row of the matrix.
+---@param e2_1 number # The first column of the second row of the matrix.
+---@param e2_2 number # The second column of the second row of the matrix.
+---@param e2_3 number # The third column of the second row of the matrix.
+---@param e2_4 number # The fourth column of the second row of the matrix.
+---@param e3_1 number # The first column of the third row of the matrix.
+---@param e3_2 number # The second column of the third row of the matrix.
+---@param e3_3 number # The third column of the third row of the matrix.
+---@param e3_4 number # The fourth column of the third row of the matrix.
+---@param e4_1 number # The first column of the fourth row of the matrix.
+---@param e4_2 number # The second column of the fourth row of the matrix.
+---@param e4_3 number # The third column of the fourth row of the matrix.
 ---@param e4_4 number # The fourth column of the fourth row of the matrix.
 ---@return love.Transform transform # The Transform object the method was called on. Allows easily chaining Transform methods.
-function Transform:setMatrix(e1_1, e1_2, ..., e4_4) end
+function Transform:setMatrix(e1_1, e1_2, e1_3, e1_4, e2_1, e2_2, e2_3, e2_4, e3_1, e3_2, e3_3, e3_4, e4_1, e4_2, e4_3, e4_4) end
 
 ---
 ---Resets the Transform to the specified transformation parameters.
diff --git a/meta/3rd/love2d/library/love/physics.lua b/meta/3rd/love2d/library/love/physics.lua
index 448d27f7b..82ea008a4 100644
--- a/meta/3rd/love2d/library/love/physics.lua
+++ b/meta/3rd/love2d/library/love/physics.lua
@@ -3122,14 +3122,12 @@ function World:queryBoundingBox(topLeftX, topLeftY, bottomRightX, bottomRightY,
 ---
 ---[Open in Browser](https://love2d.org/wiki/World:rayCast)
 ---
----@param fixture love.Fixture # The fixture intersecting the ray.
----@param x number # The x position of the intersection point.
----@param y number # The y position of the intersection point.
----@param xn number # The x value of the surface normal vector of the shape edge.
----@param yn number # The y value of the surface normal vector of the shape edge.
----@param fraction number # The position of the intersection on the ray as a number from 0 to 1 (or even higher if the ray length was changed with the return value).
----@return number control # The ray can be controlled with the return value. A positive value sets a new ray length where 1 is the default value. A value of 0 terminates the ray. If the callback function returns -1, the intersection gets ignored as if it didn't happen.
-function World:rayCast(fixture, x, y, xn, yn, fraction) end
+---@param x1 number # The x position of the starting point of the ray.
+---@param y1 number # The x position of the starting point of the ray.
+---@param x2 number # The x position of the end point of the ray.
+---@param y2 number # The x value of the surface normal vector of the shape edge.
+---@param callback function # A function called for each fixture intersected by the ray. The function gets six arguments and should return a number as a control value. The intersection points fed into the function will be in an arbitrary order. If you wish to find the closest point of intersection, you'll need to do that yourself within the function. The easiest way to do that is by using the fraction value.
+function World:rayCast(x1, y1, x2, y2, callback) end
 
 ---
 ---Sets functions for the collision callbacks during the world update.
@@ -3145,8 +3143,8 @@ function World:rayCast(fixture, x, y, xn, yn, fraction) end
 ---
 ---@param beginContact function # Gets called when two fixtures begin to overlap.
 ---@param endContact function # Gets called when two fixtures cease to overlap. This will also be called outside of a world update, when colliding objects are destroyed.
----@param preSolve function # Gets called before a collision gets resolved.
----@param postSolve function # Gets called after the collision has been resolved.
+---@param preSolve? function # Gets called before a collision gets resolved.
+---@param postSolve? function # Gets called after the collision has been resolved.
 function World:setCallbacks(beginContact, endContact, preSolve, postSolve) end
 
 ---
diff --git a/meta/3rd/lovr/library/callback.lua b/meta/3rd/lovr/library/callback.lua
index 76a536db7..903a3e8ee 100644
--- a/meta/3rd/lovr/library/callback.lua
+++ b/meta/3rd/lovr/library/callback.lua
@@ -132,6 +132,24 @@ lovr.log = nil
 ---@type fun(pass: lovr.Pass):boolean
 lovr.mirror = nil
 
+---
+---This callback is called when the mouse is moved.
+---
+---@type fun(x: number, y: number, dx: number, dy: number)
+lovr.mousemoved = nil
+
+---
+---This callback is called when a mouse button is pressed.
+---
+---@type fun(x: number, y: number, button: number)
+lovr.mousepressed = nil
+
+---
+---This callback is called when a mouse button is released.
+---
+---@type fun(x: number, y: number, button: number)
+lovr.mousereleased = nil
+
 ---
 ---This callback contains a permission response previously requested with `lovr.system.requestPermission`.
 ---
diff --git a/meta/3rd/lovr/library/lovr/audio.lua b/meta/3rd/lovr/library/lovr/audio.lua
index 99557c391..ebe357140 100644
--- a/meta/3rd/lovr/library/lovr/audio.lua
+++ b/meta/3rd/lovr/library/lovr/audio.lua
@@ -15,9 +15,7 @@ lovr.audio = {}
 ---
 ---This affects Sources that have the `absorption` effect enabled, causing audio volume to drop off with distance as it is absorbed by the medium it's traveling through (air, water, etc.).
 ---
----The difference between absorption and falloff is that absorption is more subtle and is frequency-dependent, so higher-frequency bands can get absorbed more quickly than lower ones.
----
----This can be used to apply "underwater" effects and stuff.
+---The difference between absorption and the attenuation effect is that absorption is more subtle and is frequency-dependent, so higher-frequency bands can get absorbed more quickly than lower ones. This can be used to apply "underwater" effects and stuff.
 ---
 ---
 ---### NOTE:
@@ -186,7 +184,7 @@ function lovr.audio.isStarted(type) end
 ---@overload fun(blob: lovr.Blob, options?: table):lovr.Source
 ---@overload fun(sound: lovr.Sound, options?: table):lovr.Source
 ---@param filename string # The filename of the sound to load.
----@param options? {decode: boolean, effects: table} # Optional options.
+---@param options? {decode: boolean, pitchable: boolean, spatial: boolean, effects: table} # Optional options.
 ---@return lovr.Source source # The new Source.
 function lovr.audio.newSource(filename, options) end
 
@@ -195,7 +193,7 @@ function lovr.audio.newSource(filename, options) end
 ---
 ---This affects Sources that have the `absorption` effect enabled, causing audio volume to drop off with distance as it is absorbed by the medium it's traveling through (air, water, etc.).
 ---
----The difference between absorption and falloff is that absorption is more subtle and is frequency-dependent, so higher-frequency bands can get absorbed more quickly than lower ones.
+---The difference between absorption and the attenuation effect is that absorption is more subtle and is frequency-dependent, so higher-frequency bands can get absorbed more quickly than lower ones.
 ---
 ---This can be used to apply "underwater" effects and stuff.
 ---
@@ -273,6 +271,7 @@ function lovr.audio.setGeometry(vertices, indices, material) end
 ---
 ---Sets the orientation of the virtual audio listener in angle/axis representation.
 ---
+---@overload fun(orientation: lovr.Quat)
 ---@param angle number # The number of radians the listener should be rotated around its rotation axis.
 ---@param ax number # The x component of the axis of rotation.
 ---@param ay number # The y component of the axis of rotation.
@@ -282,9 +281,14 @@ function lovr.audio.setOrientation(angle, ax, ay, az) end
 ---
 ---Sets the position and orientation of the virtual audio listener.
 ---
----@param x number # The x position of the listener, in meters.
----@param y number # The y position of the listener, in meters.
----@param z number # The z position of the listener, in meters.
+---
+---### NOTE:
+---The position of the listener doesn't use any specific units, but usually they can be thought of as meters to match the headset module.
+---
+---@overload fun(position: lovr.Vec3, orientation: lovr.Quat)
+---@param x number # The x position of the listener.
+---@param y number # The y position of the listener.
+---@param z number # The z position of the listener.
 ---@param angle number # The number of radians the listener is rotated around its axis of rotation.
 ---@param ax number # The x component of the axis of rotation.
 ---@param ay number # The y component of the axis of rotation.
@@ -292,8 +296,11 @@ function lovr.audio.setOrientation(angle, ax, ay, az) end
 function lovr.audio.setPose(x, y, z, angle, ax, ay, az) end
 
 ---
----Sets the position of the virtual audio listener, in meters.
+---Sets the position of the virtual audio listener.
+---
+---The position doesn't have any specific units, but usually they can be thought of as meters, to match the headset module.
 ---
+---@overload fun(position: lovr.Vec3)
 ---@param x number # The x position of the listener.
 ---@param y number # The y position of the listener.
 ---@param z number # The z position of the listener.
@@ -419,6 +426,20 @@ function Source:getDuration(unit) end
 ---@return number az # The z component of the axis of rotation.
 function Source:getOrientation() end
 
+---
+---Returns the pitch of the Source.
+---
+---
+---### NOTE:
+---The default pitch is 1.
+---
+---Every doubling/halving of the pitch will raise/lower the pitch by one octave.
+---
+---Changing the pitch also changes the playback speed.
+---
+---@return number pitch # The pitch.
+function Source:getPitch() end
+
 ---
 ---Returns the position and orientation of the Source.
 ---
@@ -485,7 +506,7 @@ function Source:getVolume(units) end
 ---
 ---See `lovr.audio.getSpatializer` for a table showing the effects supported by each spatializer.
 ---
----Calling this function on a Source that was created with `{ effects = false }` will always return false.
+---Calling this function on a non-spatial Source will always return false.
 ---
 ---@param effect lovr.Effect # The effect.
 ---@return boolean enabled # Whether the effect is enabled.
@@ -503,6 +524,14 @@ function Source:isLooping() end
 ---@return boolean playing # Whether the Source is playing.
 function Source:isPlaying() end
 
+---
+---Returns whether the Source was created with the `spatial` flag.
+---
+---Non-spatial sources are routed directly to the speakers and can not use effects.
+---
+---@return boolean spatial # Whether the source is spatial.
+function Source:isSpatial() end
+
 ---
 ---Pauses the source.
 ---
@@ -573,7 +602,7 @@ function Source:setDirectivity(weight, power) end
 ---
 ---See `lovr.audio.getSpatializer` for a table showing the effects supported by each spatializer.
 ---
----Calling this function on a Source that was created with `{ effects = false }` will throw an error.
+---Calling this function on a non-spatial Source will throw an error.
 ---
 ---@param effect lovr.Effect # The effect.
 ---@param enable boolean # Whether the effect should be enabled.
@@ -592,18 +621,38 @@ function Source:setLooping(loop) end
 ---
 ---Sets the orientation of the Source in angle/axis representation.
 ---
+---@overload fun(self: lovr.Source, orientation: lovr.Quat)
 ---@param angle number # The number of radians the Source should be rotated around its rotation axis.
 ---@param ax number # The x component of the axis of rotation.
 ---@param ay number # The y component of the axis of rotation.
 ---@param az number # The z component of the axis of rotation.
 function Source:setOrientation(angle, ax, ay, az) end
 
+---
+---Sets the pitch of the Source.
+---
+---
+---### NOTE:
+---The default pitch is 1.
+---
+---Every doubling/halving of the pitch will raise/lower the pitch by one octave.
+---
+---Changing the pitch also changes the playback speed.
+---
+---@param pitch number # The new pitch.
+function Source:setPitch(pitch) end
+
 ---
 ---Sets the position and orientation of the Source.
 ---
----@param x number # The x position of the Source, in meters.
----@param y number # The y position of the Source, in meters.
----@param z number # The z position of the Source, in meters.
+---
+---### NOTE:
+---The position doesn't have any defined units, but meters are used by convention.
+---
+---@overload fun(self: lovr.Source, position: lovr.Vec3, orientation: lovr.Quat)
+---@param x number # The x position of the Source.
+---@param y number # The y position of the Source.
+---@param z number # The z position of the Source.
 ---@param angle number # The number of radians the Source is rotated around its axis of rotation.
 ---@param ax number # The x component of the axis of rotation.
 ---@param ay number # The y component of the axis of rotation.
@@ -611,7 +660,7 @@ function Source:setOrientation(angle, ax, ay, az) end
 function Source:setPose(x, y, z, angle, ax, ay, az) end
 
 ---
----Sets the position of the Source, in meters.
+---Sets the position of the Source.
 ---
 ---Setting the position will cause the Source to be distorted and attenuated based on its position relative to the listener.
 ---
@@ -619,9 +668,14 @@ function Source:setPose(x, y, z, angle, ax, ay, az) end
 ---
 ---Setting the position of a stereo Source will cause an error.
 ---
----@param x number # The x coordinate.
----@param y number # The y coordinate.
----@param z number # The z coordinate.
+---
+---### NOTE:
+---The position doesn't have any defined units, but meters are used by convention.
+---
+---@overload fun(self: lovr.Source, position: lovr.Vec3)
+---@param x number # The x coordinate of the position.
+---@param y number # The y coordinate of the position.
+---@param z number # The z coordinate of the position.
 function Source:setPosition(x, y, z) end
 
 ---
@@ -763,7 +817,7 @@ function Source:tell(unit) end
 ---
 ---Decreases audio volume with distance (1 / max(distance, 1)).
 ---
----| "falloff"
+---| "attenuation"
 ---
 ---Causes audio to drop off when the Source is occluded by geometry.
 ---
diff --git a/meta/3rd/lovr/library/lovr/data.lua b/meta/3rd/lovr/library/lovr/data.lua
index babfb0147..8c8419625 100644
--- a/meta/3rd/lovr/library/lovr/data.lua
+++ b/meta/3rd/lovr/library/lovr/data.lua
@@ -150,8 +150,10 @@ function Blob:getSize() end
 ---
 ---Returns a binary string containing the Blob's data.
 ---
+---@param offset? number # A byte offset into the Blob where the string will start.
+---@param size? number # The number of bytes the string will contain.  If nil, the rest of the data in the Blob will be used, based on the `offset` parameter.
 ---@return string data # The Blob's data.
-function Blob:getString() end
+function Blob:getString(offset, size) end
 
 ---
 ---An Image stores raw 2D pixel info for `Texture`s.
@@ -205,7 +207,7 @@ function Image:getHeight() end
 ---
 ---
 ---### NOTE:
----The following texture formats are supported: `rgba`, `rgb`, `r32f`, `rg32f`, and `rgba32f`.
+---The following texture formats are supported: `r8`, `rg8`, `rgba8`, `r16`, `rg16`, `rgba16`, `r32f`, `rg32f`, `rgba32f`.
 ---
 ---@param x number # The x coordinate of the pixel to get (0-indexed).
 ---@param y number # The y coordinate of the pixel to get (0-indexed).
@@ -246,7 +248,7 @@ function Image:paste(source, x, y, fromX, fromY, width, height) end
 ---
 ---
 ---### NOTE:
----The following texture formats are supported: `rgba`, `rgb`, `r32f`, `rg32f`, and `rgba32f`.
+---The following texture formats are supported: `r8`, `rg8`, `rgba8`, `r16`, `rg16`, `rgba16`, `r32f`, `rg32f`, `rgba32f`.
 ---
 ---@param x number # The x coordinate of the pixel to set (0-indexed).
 ---@param y number # The y coordinate of the pixel to set (0-indexed).
@@ -341,7 +343,7 @@ function ModelData:getAnimationNode(index, channel) end
 ---@overload fun(self: lovr.ModelData, name: string, channel: number):lovr.AnimationProperty
 ---@param index number # The index of an animation.
 ---@param channel number # The index of a channel in the animation.
----@return lovr.AnimationProperty property # The property (translation, rotation, scale) affected by the keyframes.
+---@return lovr.AnimationProperty property # The property (translation, rotation, scale, weights) affected by the keyframes.
 function ModelData:getAnimationProperty(index, channel) end
 
 ---
@@ -353,6 +355,23 @@ function ModelData:getAnimationProperty(index, channel) end
 ---@return lovr.SmoothMode smooth # The smooth mode of the keyframes.
 function ModelData:getAnimationSmoothMode(index, channel) end
 
+---
+---Returns the number of blend shapes in the model.
+---
+---@return number count # The number of blend shapes in the model.
+function ModelData:getBlendShapeCount() end
+
+---
+---Returns the name of a blend shape in the model.
+---
+---
+---### NOTE:
+---This function will throw an error if the blend shape index is invalid.
+---
+---@param index number # The index of a blend shape.
+---@return string name # The name of the blend shape.
+function ModelData:getBlendShapeName(index) end
+
 ---
 ---Returns one of the Blobs in the model, by index.
 ---
@@ -1112,7 +1131,7 @@ function Sound:isStream() end
 function Sound:setFrames(t, count, dstOffset, srcOffset) end
 
 ---
----This indicates the different transform properties that can be animated.
+---This indicates the different node properties that can be animated.
 ---
 ---@alias lovr.AnimationProperty
 ---
@@ -1127,6 +1146,10 @@ function Sound:setFrames(t, count, dstOffset, srcOffset) end
 ---Node scale.
 ---
 ---| "scale"
+---
+---Node blend shape weights.
+---
+---| "weights"
 
 ---
 ---These are the data types that can be used by vertex data in meshes.
diff --git a/meta/3rd/lovr/library/lovr/filesystem.lua b/meta/3rd/lovr/library/lovr/filesystem.lua
index cbdb25aa9..c2a1220ba 100644
--- a/meta/3rd/lovr/library/lovr/filesystem.lua
+++ b/meta/3rd/lovr/library/lovr/filesystem.lua
@@ -3,9 +3,7 @@
 ---
 ---The `lovr.filesystem` module provides access to the filesystem.
 ---
----
----### NOTE:
----LÖVR programs can only write to a single directory, called the save directory.
+---All files written will go in a special folder called the "save directory".
 ---
 ---The location of the save directory is platform-specific:
 ---
@@ -27,15 +25,25 @@
 ---    <td><code>/sdcard/Android/data/&lt;identity&gt;/files</code></td>
 ---  </tr> </table>
 ---
----`<identity>` should be a unique identifier for your app.
+---`<identity>` is a unique identifier for the project, and can be set in `lovr.conf`.
+---
+---On Android, the identity can not be changed and will always be the package id (e.g. `org.lovr.app`).
+---
+---When files are read, they will be searched for in multiple places.
+---
+---By default, the save directory is checked first, then the project source (folder or zip).
+---
+---That way, when data is written to a file, any future reads will see the new data.
 ---
----It can be set either in `lovr.conf` or by using `lovr.filesystem.setIdentity`.
+---The `t.saveprecedence` conf setting can be used to change this precedence.
 ---
----On Android, the identity can not be changed and will always be the package id, like `org.lovr.app`.
+---Conceptually, `lovr.filesystem` uses a "virtual filesystem", which is an ordered list of folders and zip files that are merged into a single filesystem hierarchy.
 ---
----All filenames are relative to either the save directory or the directory containing the project source.
+---Folders and archives in the list can be added and removed with `lovr.filesystem.mount` and `lovr.filesystem.unmount`.
 ---
----Files in the save directory take precedence over files in the project.
+---LÖVR extends Lua's `require` function to look for modules in the virtual filesystem.
+---
+---The search patterns can be changed with `lovr.filesystem.setRequirePath`, similar to `package.path`.
 ---
 ---@class lovr.filesystem
 lovr.filesystem = {}
@@ -269,7 +277,11 @@ function lovr.filesystem.remove(path) end
 ---
 ---Set the name of the save directory.
 ---
----@param identity string # The new name of the save directory.
+---This function can only be called once and is called automatically at startup, so this function normally isn't called manually.
+---
+---However, the identity can be changed by setting the `t.identity` option in `lovr.conf`.
+---
+---@param identity string # The name of the save directory.
 function lovr.filesystem.setIdentity(identity) end
 
 ---
@@ -279,7 +291,7 @@ function lovr.filesystem.setIdentity(identity) end
 ---
 ---Any question marks in the pattern will be replaced with the module that is being required.
 ---
----It is similar to Lua\'s `package.path` variable, but the main difference is that the patterns are relative to the save directory and the project directory.
+---It is similar to Lua\'s `package.path` variable, except the patterns will be checked using `lovr.filesystem` APIs. This allows `require` to work even when the project is packaged into a zip archive, or when the project is launched from a different directory.
 ---
 ---
 ---### NOTE:
@@ -302,7 +314,7 @@ function lovr.filesystem.setRequirePath(path) end
 function lovr.filesystem.unmount(path) end
 
 ---
----Write to a file.
+---Write to a file in the save directory.
 ---
 ---
 ---### NOTE:
@@ -310,8 +322,12 @@ function lovr.filesystem.unmount(path) end
 ---
 ---If the file already has data in it, it will be replaced with the new content.
 ---
----@overload fun(filename: string, blob: lovr.Blob):number
+---If the path contains subdirectories, all of the parent directories need to exist first or the write will fail.
+---
+---Use `lovr.filesystem.createDirectory` to make sure they're created first.
+---
+---@overload fun(filename: string, blob: lovr.Blob):boolean
 ---@param filename string # The file to write to.
 ---@param content string # A string to write to the file.
----@return number bytes # The number of bytes written.
+---@return boolean success # Whether the write was successful.
 function lovr.filesystem.write(filename, content) end
diff --git a/meta/3rd/lovr/library/lovr/graphics.lua b/meta/3rd/lovr/library/lovr/graphics.lua
index 6c8f83306..956d91ae7 100644
--- a/meta/3rd/lovr/library/lovr/graphics.lua
+++ b/meta/3rd/lovr/library/lovr/graphics.lua
@@ -149,7 +149,7 @@ function lovr.graphics.getDevice() end
 ---
 ---Returns a table indicating which features are supported by the GPU.
 ---
----@return {textureBC: boolean, textureASTC: boolean, wireframe: boolean, depthClamp: boolean, indirectDrawFirstInstance: boolean, float64: boolean, int64: boolean, int16: boolean} features # 
+---@return {textureBC: boolean, textureASTC: boolean, wireframe: boolean, depthClamp: boolean, depthResolve: boolean, indirectDrawFirstInstance: boolean, float64: boolean, int64: boolean, int16: boolean} features # 
 function lovr.graphics.getFeatures() end
 
 ---
@@ -332,11 +332,22 @@ function lovr.graphics.getLimits() end
 ---### NOTE:
 ---Fun facts about render passes:
 ---
----- Textures must have the same dimensions, layer counts, and sample counts.
 ---- Textures must have been created with the `render` `TextureUsage`.
+---- Textures must have the same dimensions, layer counts, and sample counts.
+---- When rendering to textures with multiple layers, each draw will be broadcast to all layers.
+---  Render passes have multiple "views" (cameras), and each layer uses a corresponding view,
+---  allowing each layer to be rendered from a different viewpoint.
+---
+---This enables fast stereo
+---  rendering, but can also be used to efficiently render to cubemaps.
+---
+---The `ViewIndex` variable
+---  can also be used in shaders to set up any desired per-view behavior.
 ---- If `mipmap` is true, then any textures with mipmaps must have the `transfer` `TextureUsage`.
 ---- It's okay to have zero color textures, but in this case there must be a depth texture.
 ---- Setting `clear` to `false` for textures is usually very slow on mobile GPUs.
+---- It's possible to render to a specific mipmap level of a Texture, or a subset of its layers, by
+---  rendering to texture views, see `Texture:newView`.
 ---
 ---For `compute` and `transfer` passes, all of the commands in the pass act as though they run in parallel.
 ---
@@ -460,7 +471,6 @@ function lovr.graphics.newMaterial(properties) end
 ---### NOTE:
 ---Currently, the following features are not supported by the model importer:
 ---
----- glTF: Morph targets are not supported.
 ---- glTF: Only the default scene is loaded.
 ---- glTF: Currently, each skin in a Model can have up to 256 joints.
 ---- glTF: Meshes can't appear multiple times in the node hierarchy with different skins, they need
@@ -471,6 +481,10 @@ function lovr.graphics.newMaterial(properties) end
 ---
 ---Diffuse and emissive textures will be loaded using sRGB encoding, all other textures will be loaded as linear.
 ---
+---Loading a model file will fail if the asset references textures or other files using absolute paths.
+---
+---Relative paths should be used instead, and will be relative to the model file within the virtual filesystem.
+---
 ---@overload fun(blob: lovr.Blob, options?: table):lovr.Model
 ---@overload fun(modelData: lovr.ModelData, options?: table):lovr.Model
 ---@param filename string # The path to model file.
@@ -526,8 +540,8 @@ function lovr.graphics.newTally(type, count, views) end
 ---### NOTE:
 ---If no `type` is provided in the options table, LÖVR will guess the `TextureType` of the Texture based on the number of layers:
 ---
----- If there's 1 layer, the type will be `2d`.
----- If there are 6 layers, the type will be `cube`.
+---- If there's only 1 layer, the type will be `2d`.
+---- If there are 6 images provided, the type will be `cube`.
 ---- Otherwise, the type will be `array`.
 ---
 ---Note that an Image can contain multiple layers and mipmaps.
@@ -1087,6 +1101,45 @@ function Model:getAnimationDuration(index) end
 ---@return string name # The name of the animation.
 function Model:getAnimationName(index) end
 
+---
+---Returns the number of blend shapes in the model.
+---
+---@return number count # The number of blend shapes in the model.
+function Model:getBlendShapeCount() end
+
+---
+---Returns the name of a blend shape in the model.
+---
+---
+---### NOTE:
+---This function will throw an error if the blend shape index is invalid.
+---
+---@param index number # The index of a blend shape.
+---@return string name # The name of the blend shape.
+function Model:getBlendShapeName(index) end
+
+---
+---Returns the weight of a blend shape.
+---
+---A blend shape contains offset values for the vertices of one of the meshes in a Model.
+---
+---Whenever the Model is drawn, the offsets are multiplied by the weight of the blend shape, allowing for smooth blending between different meshes.
+---
+---A weight of zero won't apply any displacement and will skip processing of the blend shape.
+---
+---
+---### NOTE:
+---The initial weights are declared in the model file.
+---
+---Weights can be any number, but usually they're kept between 0 and 1.
+---
+---This function will throw an error if the blend shape name or index doesn't exist.
+---
+---@overload fun(self: lovr.Model, name: string):number
+---@param index number # The index of a blend shape.
+---@return number weight # The weight of the blend shape.
+function Model:getBlendShapeWeight(index) end
+
 ---
 ---Returns the 6 values of the Model's axis-aligned bounding box.
 ---
@@ -1277,10 +1330,6 @@ function Model:getNodePosition(index, space) end
 ---
 ---Returns the scale of a node.
 ---
----
----### NOTE:
----For best results when animating, it's recommended to keep the 3 components of the scale the same.
----
 ---@overload fun(self: lovr.Model, name: string, origin?: lovr.OriginType):number, number, number
 ---@param index number # The index of the node.
 ---@param origin? lovr.OriginType # Whether the scale should be returned relative to the root node or the node's parent.
@@ -1394,59 +1443,128 @@ function Model:hasJoints() end
 ---
 function Model:resetNodeTransforms() end
 
+---
+---Sets the weight of a blend shape.
+---
+---A blend shape contains offset values for the vertices of one of the meshes in a Model.
+---
+---Whenever the Model is drawn, the offsets are multiplied by the weight of the blend shape, allowing for smooth blending between different meshes.
+---
+---A weight of zero won't apply any displacement and will skip processing of the blend shape.
+---
+---
+---### NOTE:
+---The initial weights are declared in the model file.
+---
+---Weights can be any number, but usually they're kept between 0 and 1.
+---
+---This function will throw an error if the blend shape name or index doesn't exist.
+---
+---@overload fun(self: lovr.Model, name: string, weight: number)
+---@param index number # The index of a blend shape.
+---@param weight number # The new weight for the blend shape.
+function Model:setBlendShapeWeight(index, weight) end
+
 ---
 ---Sets or blends the orientation of a node to a new orientation.
 ---
----@overload fun(self: lovr.Model, name: string, orientation: lovr.rotation, blend?: number)
+---This sets the local orientation of the node, relative to its parent.
+---
+---@overload fun(self: lovr.Model, name: string, angle: number, ax: number, ay: number, az: number, blend?: number)
+---@overload fun(self: lovr.Model, index: number, orientation: lovr.Quat, blend?: number)
+---@overload fun(self: lovr.Model, name: string, orientation: lovr.Quat, blend?: number)
 ---@param index number # The index of the node.
----@param orientation lovr.rotation # The target orientation.
+---@param angle number # The number of radians the node should be rotated around its rotation axis.
+---@param ax number # The x component of the axis of rotation.
+---@param ay number # The y component of the axis of rotation.
+---@param az number # The z component of the axis of rotation.
 ---@param blend? number # A number from 0 to 1 indicating how much of the target orientation to blend in.  A value of 0 will not change the node's orientation at all, whereas 1 will fully blend to the target orientation.
-function Model:setNodeOrientation(index, orientation, blend) end
+function Model:setNodeOrientation(index, angle, ax, ay, az, blend) end
 
 ---
 ---Sets or blends the pose (position and orientation) of a node to a new pose.
 ---
+---This sets the local pose of the node, relative to its parent.
+---
+---The scale will remain unchanged.
+---
+---@overload fun(self: lovr.Model, name: string, x: number, y: number, z: number, angle: number, ax: number, ay: number, az: number, blend?: number)
+---@overload fun(self: lovr.Model, index: number, position: lovr.Vec3, orientation: lovr.Quat, blend?: number)
 ---@overload fun(self: lovr.Model, name: string, position: lovr.Vec3, orientation: lovr.Quat, blend?: number)
 ---@param index number # The index of the node.
----@param position lovr.Vec3 # The target position.  Can also be provided as 3 numbers.
----@param orientation lovr.Quat # The target orientation.  Can also be provided as 4 numbers in angle-axis form.
+---@param x number # The x component of the position.
+---@param y number # The y component of the position.
+---@param z number # The z component of the position.
+---@param angle number # The number of radians the node should be rotated around its rotation axis.
+---@param ax number # The x component of the axis of rotation.
+---@param ay number # The y component of the axis of rotation.
+---@param az number # The z component of the axis of rotation.
 ---@param blend? number # A number from 0 to 1 indicating how much of the target pose to blend in.  A value of 0 will not change the node's pose at all, whereas 1 will fully blend to the target pose.
-function Model:setNodePose(index, position, orientation, blend) end
+function Model:setNodePose(index, x, y, z, angle, ax, ay, az, blend) end
 
 ---
----Sets or blends the position of a node to a new position.
+---Sets or blends the position of a node.
 ---
+---This sets the local position of the node, relative to its parent.
+---
+---@overload fun(self: lovr.Model, name: string, x: number, y: number, z: number, blend?: number)
+---@overload fun(self: lovr.Model, index: number, position: lovr.Vec3, blend?: number)
 ---@overload fun(self: lovr.Model, name: string, position: lovr.Vec3, blend?: number)
 ---@param index number # The index of the node.
----@param position lovr.Vec3 # The target position.  Can also be provided as 3 numbers.
----@param blend? number # A number from 0 to 1 indicating how much of the target position to blend in.  A value of 0 will not change the node's position at all, whereas 1 will fully blend to the target position.
-function Model:setNodePosition(index, position, blend) end
+---@param x number # The x coordinate of the new position.
+---@param y number # The y coordinate of the new position.
+---@param z number # The z coordinate of the new position.
+---@param blend? number # A number from 0 to 1 indicating how much of the new position to blend in.  A value of 0 will not change the node's position at all, whereas 1 will fully blend to the target position.
+function Model:setNodePosition(index, x, y, z, blend) end
 
 ---
 ---Sets or blends the scale of a node to a new scale.
 ---
+---This sets the local scale of the node, relative to its parent.
+---
 ---
 ---### NOTE:
----For best results when animating, it's recommended to keep the 3 components of the scale the same.
+---For best results when animating, it's recommended to keep the 3 scale components the same.
 ---
+---@overload fun(self: lovr.Model, name: string, sx: number, sy: number, sz: number, blend?: number)
+---@overload fun(self: lovr.Model, index: number, scale: lovr.Vec3, blend?: number)
 ---@overload fun(self: lovr.Model, name: string, scale: lovr.Vec3, blend?: number)
 ---@param index number # The index of the node.
----@param scale lovr.Vec3 # The target scale.  Can also be provided as 3 numbers.
----@param blend? number # A number from 0 to 1 indicating how much of the target scale to blend in.  A value of 0 will not change the node's scale at all, whereas 1 will fully blend to the target scale.
-function Model:setNodeScale(index, scale, blend) end
+---@param sx number # The x scale.
+---@param sy number # The y scale.
+---@param sz number # The z scale.
+---@param blend? number # A number from 0 to 1 indicating how much of the new scale to blend in.  A value of 0 will not change the node's scale at all, whereas 1 will fully blend to the target scale.
+function Model:setNodeScale(index, sx, sy, sz, blend) end
 
 ---
 ---Sets or blends the transform of a node to a new transform.
 ---
+---This sets the local transform of the node, relative to its parent.
+---
 ---
 ---### NOTE:
 ---For best results when animating, it's recommended to keep the 3 components of the scale the same.
 ---
+---Even though the translation, scale, and rotation parameters are given in TSR order, they are applied in the normal TRS order.
+---
+---@overload fun(self: lovr.Model, name: string, x: number, y: number, z: number, sx: number, sy: number, sz: number, angle: number, ax: number, ay: number, az: number, blend?: number)
+---@overload fun(self: lovr.Model, index: number, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, blend?: number)
+---@overload fun(self: lovr.Model, name: string, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, blend?: number)
+---@overload fun(self: lovr.Model, index: number, transform: lovr.Mat4, blend?: number)
 ---@overload fun(self: lovr.Model, name: string, transform: lovr.Mat4, blend?: number)
 ---@param index number # The index of the node.
----@param transform lovr.Mat4 # The target transform.  Can also be provided as position, scale, and rotation using a mix of `Vectors` or numbers, with 3 scale components.
+---@param x number # The x component of the position.
+---@param y number # The y component of the position.
+---@param z number # The z component of the position.
+---@param sx number # The x component of the scale.
+---@param sy number # The y component of the scale.
+---@param sz number # The z component of the scale.
+---@param angle number # The number of radians the node should be rotated around its rotation axis.
+---@param ax number # The x component of the axis of rotation.
+---@param ay number # The y component of the axis of rotation.
+---@param az number # The z component of the axis of rotation.
 ---@param blend? number # A number from 0 to 1 indicating how much of the target transform to blend in.  A value of 0 will not change the node's transform at all, whereas 1 will fully blend to the target transform.
-function Model:setNodeTransform(index, transform, blend) end
+function Model:setNodeTransform(index, x, y, z, sx, sy, sz, angle, ax, ay, az, blend) end
 
 ---
 ---Pass objects are used to record commands for the GPU.
@@ -1477,7 +1595,7 @@ local Pass = {}
 ---
 ---Similar to `Pass:copy`, except the source and destination sizes can be different.
 ---
----The pixels from the source texture will be scaled to the destination size.
+---The pixels from the source texture will be scaled to the destination size. This can only be called on a transfer pass, which can be created with `lovr.graphics.getPass`.
 ---
 ---
 ---### NOTE:
@@ -1509,9 +1627,20 @@ function Pass:blit(src, dst, srcx, srcy, srcz, dstx, dsty, dstz, srcw, srch, src
 ---
 ---This is like `Pass:cube`, except it takes 3 separate values for the scale.
 ---
----@param transform lovr.Mat4 # The transform of the box.  Can also be provided as position, 3-component scale, and rotation using a mix of `Vectors` or numbers.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, size: lovr.Vec3, orientation: lovr.Quat, style?: lovr.DrawStyle)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, style?: lovr.DrawStyle)
+---@param x? number # The x coordinate of the center of the box.
+---@param y? number # The y coordinate of the center of the box.
+---@param z? number # The z coordinate of the center of the box.
+---@param width? number # The width of the box.
+---@param height? number # The height of the box.
+---@param depth? number # The depth of the box.
+---@param angle? number # The rotation of the box around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param style? lovr.DrawStyle # Whether the box should be drawn filled or outlined.
-function Pass:box(transform, style) end
+function Pass:box(x, y, z, width, height, depth, angle, ax, ay, az, style) end
 
 ---
 ---Draws a capsule.
@@ -1524,28 +1653,51 @@ function Pass:box(transform, style) end
 ---
 ---The local origin of the capsule is in the center, and the local z axis points towards the end caps.
 ---
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, segments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, segments?: number)
 ---@overload fun(self: lovr.Pass, p1: lovr.Vec3, p2: lovr.Vec3, radius?: number, segments?: number)
----@param transform lovr.Mat4 # The transform of the capsule.  Can also be provided as position, scale, and rotation using a mix of `Vectors` or numbers.  When using numbers for the scale, 2 should be provided: one for the radius and one for the length.  When using a matrix or a vector for the scale, the X and Y components are the radius and the Z component is the length.
+---@param x? number # The x coordinate of the center of the capsule.
+---@param y? number # The y coordinate of the center of the capsule.
+---@param z? number # The z coordinate of the center of the capsule.
+---@param radius? number # The radius of the capsule.
+---@param length? number # The length of the capsule.
+---@param angle? number # The rotation of the capsule around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param segments? number # The number of circular segments to render.
-function Pass:capsule(transform, segments) end
+function Pass:capsule(x, y, z, radius, length, angle, ax, ay, az, segments) end
 
 ---
 ---Draws a circle.
 ---
 ---
 ---### NOTE:
----The local origin of the circle is in its center, and the local z axis goes through the center.
+---The local origin of the circle is in its center.
 ---
----@param transform lovr.Mat4 # The transform of the circle.  Can also be provided as position, radius, and rotation, using a mix of `Vectors` or numbers.
+---The local z axis is perpendicular to the circle.
+---
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, radius?: number, orientation: lovr.Quat, style?: lovr.DrawStyle, angle1?: number, angle2?: number, segments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, style?: lovr.DrawStyle, angle1?: number, angle2?: number, segments?: number)
+---@param x? number # The x coordinate of the center of the circle.
+---@param y? number # The y coordinate of the center of the circle.
+---@param z? number # The z coordinate of the center of the circle.
+---@param radius? number # The radius of the circle.
+---@param angle? number # The rotation of the circle around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param style? lovr.DrawStyle # Whether the circle should be filled or outlined.
 ---@param angle1? number # The angle of the beginning of the arc.
 ---@param angle2? number # angle of the end of the arc.
 ---@param segments? number # The number of segments to render.
-function Pass:circle(transform, style, angle1, angle2, segments) end
+function Pass:circle(x, y, z, radius, angle, ax, ay, az, style, angle1, angle2, segments) end
 
 ---
 ---Clears a Buffer or Texture.
 ---
+---This can only be called on a transfer pass, which can be created with `lovr.graphics.getPass`.
+---
 ---@overload fun(self: lovr.Pass, texture: lovr.Texture, color: lovr.Vec4, layer?: number, layers?: number, level?: number, levels?: number)
 ---@param buffer lovr.Buffer # The Buffer to clear.
 ---@param index? number # The index of the first item to clear.
@@ -1555,24 +1707,59 @@ function Pass:clear(buffer, index, count) end
 ---
 ---Runs a compute shader.
 ---
----Compute shaders are run in 3D grids of workgroups.
+---Before calling this, a compute shader needs to be active, using `Pass:setShader`.
 ---
----Each local workgroup is itself a 3D grid of invocations, declared using `local_size_x`, `local_size_y`, and `local_size_z` in the shader code.
+---This can only be called on a Pass with the `compute` type, which can be created using `lovr.graphics.getPass`.
 ---
 ---
 ---### NOTE:
----All these 3D grids can get confusing, but the basic idea is to make the local workgroup size a small block of e.g. 8x8 pixels or 4x4x4 voxels, and then dispatch however many global workgroups are needed to cover an image or voxel field.
+---Usually compute shaders are run many times in parallel: once for each pixel in an image, once per particle, once per object, etc.
+---
+---The 3 arguments represent how many times to run, or "dispatch", the compute shader, in up to 3 dimensions.
+---
+---Each element of this grid is called a **workgroup**.
+---
+---To make things even more complicated, each workgroup itself is made up of a set of "mini GPU threads", which are called **local workgroups**.
+---
+---Like workgroups, the local workgroup size can also be 3D.
+---
+---It's declared in the shader code, like this:
+---
+---    layout(local_size_x = w, local_size_y = h, local_size_z = d) in;
+---
+---All these 3D grids can get confusing, but the basic idea is to make the local workgroup size a small block of e.g. 32 particles or 8x8 pixels or 4x4x4 voxels, and then dispatch however many workgroups are needed to cover a list of particles, image, voxel field, etc.
+---
+---The reason to do it this way is that the GPU runs its threads in little fixed-size bundles called subgroups.
+---
+---Subgroups are usually 32 or 64 threads (the exact size is given by the `subgroupSize` property of `lovr.graphics.getDevice`) and all run together.
+---
+---If the local workgroup size was `1x1x1`, then the GPU would only run 1 thread per subgroup and waste the other 31 or 63.
+---
+---So for the best performance, be sure to set a local workgroup size bigger than 1!
+---
+---Inside the compute shader, a few builtin variables can be used to figure out which workgroup is running:
+---
+---- `uvec3 WorkgroupCount` is the workgroup count per axis (the `Pass:compute` arguments).
+---- `uvec3 WorkgroupSize` is the local workgroup size (declared in the shader).
+---- `uvec3 WorkgroupID` is the index of the current (global) workgroup.
+---- `uvec3 LocalThreadID` is the index of the local workgroup inside its workgroup.
+---- `uint LocalThreadIndex` is a 1D version of `LocalThreadID`.
+---- `uvec3 GlobalThreadID` is the unique identifier for a thread within all workgroups in a
+---  dispatch. It's equivalent to `WorkgroupID * WorkgroupSize + LocalThreadID` (usually what you
+---  want!)
 ---
----The reason to do it this way is that the GPU runs invocations in bundles called subgroups.
+---Indirect compute dispatches are useful to "chain" compute shaders together, while keeping all of the data on the GPU.
 ---
----Subgroups are usually 32 or 64 invocations (the exact size is given by the `subgroupSize` property of `lovr.graphics.getDevice`).
+---The first dispatch can do some computation and write some results to buffers, then the second indirect dispatch can use the data in those buffers to know how many times it should run.
 ---
----If the local workgroup size was `1x1x1`, then the GPU would only run 1 invocation per subgroup and waste the other 31 or 63.
+---An example would be a compute shader that does some sort of object culling, writing the number of visible objects to a buffer along with the IDs of each one. Subsequent compute shaders can be indirectly dispatched to perform extra processing on the visible objects.
+---
+---Finally, an indirect draw can be used to render them.
 ---
 ---@overload fun(self: lovr.Pass, buffer: lovr.Buffer, offset?: number)
----@param x? number # How many workgroups to dispatch in the x dimension.
----@param y? number # How many workgroups to dispatch in the y dimension.
----@param z? number # How many workgroups to dispatch in the z dimension.
+---@param x? number # The number of workgroups to dispatch in the x dimension.
+---@param y? number # The number of workgroups to dispatch in the y dimension.
+---@param z? number # The number of workgroups to dispatch in the z dimension.
 function Pass:compute(x, y, z) end
 
 ---
@@ -1582,14 +1769,25 @@ function Pass:compute(x, y, z) end
 ---### NOTE:
 ---The local origin is at the center of the base of the cone, and the negative z axis points towards the tip.
 ---
----@param transform lovr.Mat4 # The transform of the cone.  Can also be provided as position, scale, and rotation using a mix of `Vectors` or numbers.  When using numbers for the scale, 2 should be provided: one for the radius and one for the length.  When using a matrix or a vector for the scale, the X and Y components are the radius and the Z component is the length.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, segments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, segments?: number)
+---@overload fun(self: lovr.Pass, p1: lovr.Vec3, p2: lovr.Vec3, radius?: number, segments?: number)
+---@param x? number # The x coordinate of the center of the base of the cone.
+---@param y? number # The y coordinate of the center of the base of the cone.
+---@param z? number # The z coordinate of the center of the base of the cone.
+---@param radius? number # The radius of the cone.
+---@param length? number # The length of the cone.
+---@param angle? number # The rotation of the cone around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param segments? number # The number of segments in the cone.
-function Pass:cone(transform, segments) end
+function Pass:cone(x, y, z, radius, length, angle, ax, ay, az, segments) end
 
 ---
 ---Copies data to or between `Buffer` and `Texture` objects.
 ---
----This function must be called on a `transfer` pass.
+---This can only be called on a transfer pass, which can be created with `lovr.graphics.getPass`.
 ---
 ---@overload fun(self: lovr.Pass, blob: lovr.Blob, bufferdst: lovr.Buffer, srcoffset?: number, dstoffset?: number, size?: number)
 ---@overload fun(self: lovr.Pass, buffersrc: lovr.Buffer, bufferdst: lovr.Buffer, srcoffset?: number, dstoffset?: number, size?: number)
@@ -1610,9 +1808,18 @@ function Pass:copy(table, bufferdst, srcindex, dstindex, count) end
 ---### NOTE:
 ---The local origin is in the center of the cube.
 ---
----@param transform lovr.Mat4 # The transform of the cube.  Can also be provided as position, 1-component scale, and rotation using a mix of `Vectors` or numbers.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, size?: number, orientation: lovr.Quat, style?: lovr.DrawStyle)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, style?: lovr.DrawStyle)
+---@param x? number # The x coordinate of the center of the cube.
+---@param y? number # The y coordinate of the center of the cube.
+---@param z? number # The z coordinate of the center of the cube.
+---@param size? number # The size of the cube.
+---@param angle? number # The rotation of the cube around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param style? lovr.DrawStyle # Whether the cube should be drawn filled or outlined.
-function Pass:cube(transform, style) end
+function Pass:cube(x, y, z, size, angle, ax, ay, az, style) end
 
 ---
 ---Draws a cylinder.
@@ -1621,24 +1828,45 @@ function Pass:cube(transform, style) end
 ---### NOTE:
 ---The local origin is in the center of the cylinder, and the length of the cylinder is along the z axis.
 ---
----@overload fun(self: lovr.Pass, p1: lovr.Vec3, p2: lovr.Vec3, radius: number, capped?: boolean, angle1?: number, angle2?: number, segments?: number)
----@param transform lovr.Mat4 # The transform of the cylinder.  Can also be provided as position, scale, and rotation using a mix of `Vectors` or numbers.  When using numbers for the scale, 2 should be provided: one for the radius and one for the length.  When using a matrix or a vector for the scale, the X and Y components are the radius and the Z component is the length.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, capped?: boolean, angle1?: number, angle2?: number, segments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, capped?: boolean, angle1?: number, angle2?: number, segments?: number)
+---@overload fun(self: lovr.Pass, p1: lovr.Vec3, p2: lovr.Vec3, radius?: number, capped?: boolean, angle1?: number, angle2?: number, segments?: number)
+---@param x? number # The x coordinate of the center of the cylinder.
+---@param y? number # The y coordinate of the center of the cylinder.
+---@param z? number # The z coordinate of the center of the cylinder.
+---@param radius? number # The radius of the cylinder.
+---@param length? number # The length of the cylinder.
+---@param angle? number # The rotation of the cylinder around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param capped? boolean # Whether the tops and bottoms of the cylinder should be rendered.
 ---@param angle1? number # The angle of the beginning of the arc.
 ---@param angle2? number # angle of the end of the arc.
 ---@param segments? number # The number of circular segments to render.
-function Pass:cylinder(transform, capped, angle1, angle2, segments) end
+function Pass:cylinder(x, y, z, radius, length, angle, ax, ay, az, capped, angle1, angle2, segments) end
 
 ---
 ---Draws a model.
 ---
+---@overload fun(self: lovr.Pass, model: lovr.Model, position: lovr.Vec3, scale?: number, orientation: lovr.Quat, nodeindex?: number, children?: boolean, instances?: number)
+---@overload fun(self: lovr.Pass, model: lovr.Model, transform: lovr.Mat4, nodeindex?: number, children?: boolean, instances?: number)
+---@overload fun(self: lovr.Pass, model: lovr.Model, x?: number, y?: number, z?: number, scale?: number, angle?: number, ax?: number, ay?: number, az?: number, nodename?: string, children?: boolean, instances?: number)
+---@overload fun(self: lovr.Pass, model: lovr.Model, position: lovr.Vec3, scale?: number, orientation: lovr.Quat, nodename?: string, children?: boolean, instances?: number)
 ---@overload fun(self: lovr.Pass, model: lovr.Model, transform: lovr.Mat4, nodename?: string, children?: boolean, instances?: number)
 ---@param model lovr.Model # The model to draw.
----@param transform lovr.Mat4 # The transform of the model.  Can also be provided as a position, 1-component scale, and rotation using a combination of `Vectors` and numbers.
+---@param x? number # The x coordinate to draw the model at.
+---@param y? number # The y coordinate to draw the model at.
+---@param z? number # The z coordinate to draw the model at.
+---@param scale? number # The scale of the model.
+---@param angle? number # The rotation of the model around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param nodeindex? number # The index of the node to draw.  If nil, the root node is drawn.
 ---@param children? boolean # Whether the children of the node should be drawn.
 ---@param instances? number # The number of instances to draw.
-function Pass:draw(model, transform, nodeindex, children, instances) end
+function Pass:draw(model, x, y, z, scale, angle, ax, ay, az, nodeindex, children, instances) end
 
 ---
 ---Draws a fullscreen triangle.
@@ -1714,6 +1942,7 @@ function Pass:getTarget() end
 ---
 ---The type restricts what kinds of functions can be called on the pass.
 ---
+---@return lovr.PassType type # The type of the Pass.
 function Pass:getType() end
 
 ---
@@ -1783,25 +2012,39 @@ function Pass:line(x1, y1, z1, x2, y2, z2, ...) end
 ---
 ---It can be used to reorder, reuse, or omit vertices from the mesh.
 ---
+---When drawing without a vertex buffer, the `VertexIndex` variable can be used in shaders to compute the position of each vertex, possibly by reading data from other `Buffer` or `Texture` resources.
+---
 ---The active `MeshMode` controls whether the vertices are drawn as points, lines, or triangles.
 ---
 ---The active `Material` is applied to the mesh.
 ---
+---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, position: lovr.Vec3, scales: lovr.Vec3, orientation: lovr.Quat, start?: number, count?: number, instances?: number)
+---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, transform: lovr.Mat4, start?: number, count?: number, instances?: number)
+---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, indices: lovr.Buffer, x?: number, y?: number, z?: number, scale?: number, angle?: number, ax?: number, ay?: number, az?: number, start?: number, count?: number, instances?: number, base?: number)
+---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, indices: lovr.Buffer, position: lovr.Vec3, scales: lovr.Vec3, orientation: lovr.Quat, start?: number, count?: number, instances?: number, base?: number)
 ---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, indices: lovr.Buffer, transform: lovr.Mat4, start?: number, count?: number, instances?: number, base?: number)
 ---@overload fun(self: lovr.Pass, vertices?: lovr.Buffer, indices: lovr.Buffer, draws: lovr.Buffer, drawcount: number, offset: number, stride: number)
----@overload fun(self: lovr.Pass, vertexcount: number, transform: lovr.Mat4)
----@overload fun(self: lovr.Pass, vertexcount: number, indices: lovr.Buffer, transform: lovr.Mat4)
 ---@param vertices? lovr.Buffer # The buffer containing the vertices to draw.
----@param transform lovr.Mat4 # The transform to apply to the mesh.  Can also be provided as a position, 1-component scale, and rotation using a combination of `Vectors` and numbers.
+---@param x? number # The x coordinate of the position to draw the mesh at.
+---@param y? number # The y coordinate of the position to draw the mesh at.
+---@param z? number # The z coordinate of the position to draw the mesh at.
+---@param scale? number # The scale of the mesh.
+---@param angle? number # The number of radians the mesh is rotated around its rotational axis.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param start? number # The 1-based index of the first vertex to render from the vertex buffer (or the first index, when using an index buffer).
 ---@param count? number # The number of vertices to render (or the number of indices, when using an index buffer). When `nil`, as many vertices or indices as possible will be drawn (based on the length of the Buffers and `start`).
 ---@param instances? number # The number of copies of the mesh to render.
----@param base? number # A base offset to apply to vertex indices.
-function Pass:mesh(vertices, transform, start, count, instances, base) end
+function Pass:mesh(vertices, x, y, z, scale, angle, ax, ay, az, start, count, instances) end
 
 ---
 ---Generates mipmaps for a texture.
 ---
+---This can only be called on a transfer pass, which can be created with `lovr.graphics.getPass`.
+---
+---When rendering to textures with a render pass, it's also possible to automatically regenerate mipmaps after rendering by adding the `mipmaps` flag when creating the pass.
+---
 ---@param texture lovr.Texture # The texture to mipmap.
 ---@param base? number # The index of the mipmap used to generate the remaining mipmaps.
 ---@param count? number # The number of mipmaps to generate.  If nil, generates the remaining mipmaps.
@@ -1815,11 +2058,21 @@ function Pass:origin() end
 ---
 ---Draws a plane.
 ---
----@param transform lovr.Mat4 # The transform of the plane.  Can also be provided as a position, 2-component scale, and rotation using a combination of `Vectors`, and numbers.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, size: lovr.Vec2, orientation: lovr.Quat, style?: lovr.DrawStyle, columns?: number, rows?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, style?: lovr.DrawStyle, columns?: number, rows?: number)
+---@param x? number # The x coordinate of the center of the plane.
+---@param y? number # The y coordinate of the center of the plane.
+---@param z? number # The z coordinate of the center of the plane.
+---@param width? number # The width of the plane.
+---@param height? number # The height of the plane.
+---@param angle? number # The rotation of the plane around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param style? lovr.DrawStyle # Whether the plane should be drawn filled or outlined.
 ---@param columns? number # The number of horizontal segments in the plane.
 ---@param rows? number # The number of vertical segments in the plane.
-function Pass:plane(transform, style, columns, rows) end
+function Pass:plane(x, y, z, width, height, angle, ax, ay, az, style, columns, rows) end
 
 ---
 ---Draws points.
@@ -1871,7 +2124,9 @@ function Pass:push(stack) end
 ---
 ---Creates a `Readback` object which asynchronously downloads data from a `Buffer`, `Texture`, or `Tally`.
 ---
----The readback can be polled for completion, or, after this transfer pass is completed, `Readback:wait` can be used to block until the download is complete.
+---The readback can be polled for completion, or, after this transfer pass is submitted, `Readback:wait` can be used to block until the download is complete.
+---
+---This can only be called on a transfer pass, which can be created with `lovr.graphics.getPass`.
 ---
 ---@overload fun(self: lovr.Pass, texture: lovr.Texture, x?: number, y?: number, layer?: number, level?: number, width?: number, height?: number):lovr.Readback
 ---@overload fun(self: lovr.Pass, tally: lovr.Tally, index: number, count: number):lovr.Readback
@@ -1884,19 +2139,45 @@ function Pass:read(buffer, index, count) end
 ---
 ---Rotates the coordinate system.
 ---
----@param rotation lovr.Quat # A quaternion containing the rotation to apply.  Can also be provided as 4 numbers in angle-axis representation.
-function Pass:rotate(rotation) end
+---@overload fun(self: lovr.Pass, rotation: lovr.Quat)
+---@param angle number # The amount to rotate the coordinate system by, in radians.
+---@param ax number # The x component of the axis of rotation.
+---@param ay number # The y component of the axis of rotation.
+---@param az number # The z component of the axis of rotation.
+function Pass:rotate(angle, ax, ay, az) end
+
+---
+---Draws a rounded rectangle.
+---
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, size: lovr.Vec3, orientation: lovr.Quat, radius?: number, segments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, radius?: number, segments?: number)
+---@param x? number # The x coordinate of the center of the rectangle.
+---@param y? number # The y coordinate of the center of the rectangle.
+---@param z? number # The z coordinate of the center of the rectangle.
+---@param width? number # The width of the rectangle.
+---@param height? number # The height of the rectangle.
+---@param thickness? number # The thickness of the rectangle.
+---@param angle? number # The rotation of the rectangle around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
+---@param radius? number # The radius of the rectangle corners.  If the radius is zero or negative, the rectangle will have sharp corners.
+---@param segments? number # The number of circular segments to use for each corner.  This increases the smoothness, but increases the number of vertices in the mesh.
+function Pass:roundrect(x, y, z, width, height, thickness, angle, ax, ay, az, radius, segments) end
 
 ---
 ---Scales the coordinate system.
 ---
----@param scale lovr.Vec3 # The scale to apply to the coordinate system.  Can also be provided as 1 or 3 numbers.
-function Pass:scale(scale) end
+---@overload fun(self: lovr.Pass, scale: lovr.Vec3)
+---@param sx number # The x component of the scale.
+---@param sy? number # The y component of the scale.
+---@param sz? number # The z component of the scale.
+function Pass:scale(sx, sy, sz) end
 
 ---
 ---Sends a value to a variable in the Pass's active `Shader`.
 ---
----The active shader is changed using using `Pass:setShader`.
+---The active shader is changed using `Pass:setShader`.
 ---
 ---
 ---### NOTE:
@@ -1911,12 +2192,14 @@ function Pass:scale(scale) end
 ---@overload fun(self: lovr.Pass, name: string, texture: lovr.Texture)
 ---@overload fun(self: lovr.Pass, name: string, sampler: lovr.Sampler)
 ---@overload fun(self: lovr.Pass, name: string, constant: any)
----@overload fun(self: lovr.Pass, binding: number, buffer: lovr.Buffer)
+---@overload fun(self: lovr.Pass, binding: number, buffer: lovr.Buffer, offset?: number, extent?: number)
 ---@overload fun(self: lovr.Pass, binding: number, texture: lovr.Texture)
 ---@overload fun(self: lovr.Pass, binding: number, sampler: lovr.Sampler)
 ---@param name string # The name of the Shader variable.
 ---@param buffer lovr.Buffer # The Buffer to assign.
-function Pass:send(name, buffer) end
+---@param offset? number # An offset from the start of the buffer where data will be read, in bytes.
+---@param extent? number # The number of bytes that will be available for reading.  If zero, as much data as possible will be bound, depending on the offset, buffer size, and the `uniformBufferRange` or `storageBufferRange` limit.
+function Pass:send(name, buffer, offset, extent) end
 
 ---
 ---Sets whether alpha to coverage is enabled.
@@ -1943,6 +2226,7 @@ function Pass:setAlphaToCoverage(enable) end
 ---### NOTE:
 ---The default blend mode is `alpha` with the `alphamultiply` alpha mode.
 ---
+---@overload fun(self: lovr.Pass)
 ---@param blend lovr.BlendMode # The blend mode.
 ---@param alphaBlend lovr.BlendAlphaMode # The alpha blend mode, used to control premultiplied alpha.
 function Pass:setBlendMode(blend, alphaBlend) end
@@ -2179,6 +2463,10 @@ function Pass:setShader(shader) end
 ---### NOTE:
 ---The stencil test is disabled by default.
 ---
+---Setting the stencil test requires the `Pass` to have a depth texture with the `d24s8` or `d32fs8` format (the `s` means "stencil").
+---
+---The `t.graphics.stencil` and `t.headset.stencil` flags in `lovr.conf` can be used to request a stencil format for the default window and headset passes, respectively.
+---
 ---@overload fun(self: lovr.Pass)
 ---@param test lovr.CompareMode # The new stencil test to use.
 ---@param value number # The stencil value to compare against.
@@ -2194,6 +2482,10 @@ function Pass:setStencilTest(test, value, mask) end
 ---### NOTE:
 ---By default, stencil writes are disabled.
 ---
+---Setting the stencil test requires the `Pass` to have a depth texture with the `d24s8` or `d32fs8` format (the `s` means "stencil").
+---
+---The `t.graphics.stencil` and `t.headset.stencil` flags in `lovr.conf` can be used to request a stencil format for the default window and headset passes, respectively.
+---
 ---@overload fun(self: lovr.Pass, actions: table, value?: number, mask?: number)
 ---@overload fun(self: lovr.Pass)
 ---@param action lovr.StencilAction # How pixels drawn will update the stencil buffer.
@@ -2212,6 +2504,7 @@ function Pass:setStencilWrite(action, value, mask) end
 ---
 ---These view poses are also available using `lovr.headset.getViewPose`.
 ---
+---@overload fun(self: lovr.Pass, view: number, position: lovr.Vec3, orientation: lovr.Quat)
 ---@overload fun(self: lovr.Pass, view: number, matrix: lovr.Mat4, inverted: boolean)
 ---@param view number # The index of the view to update.
 ---@param x number # The x position of the viewer, in meters.
@@ -2296,10 +2589,19 @@ function Pass:skybox(skybox) end
 ---### NOTE:
 ---The local origin of the sphere is in its center.
 ---
----@param transform lovr.Mat4 # The transform of the sphere.  Can also be provided as a position, radius, and rotation using a mix of `Vectors and numbers.'
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, radius?: number, orientation: lovr.Quat, longitudes?: number, latitudes?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, longitudes?: number, latitudes?: number)
+---@param x? number # The x coordinate of the center of the sphere.
+---@param y? number # The y coordinate of the center of the sphere.
+---@param z? number # The z coordinate of the center of the sphere.
+---@param radius? number # The radius of the sphere.
+---@param angle? number # The rotation of the sphere around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param longitudes? number # The number of "horizontal" segments.
 ---@param latitudes? number # The number of "vertical" segments.
-function Pass:sphere(transform, longitudes, latitudes) end
+function Pass:sphere(x, y, z, radius, angle, ax, ay, az, longitudes, latitudes) end
 
 ---
 ---Draws text.
@@ -2326,13 +2628,24 @@ function Pass:sphere(transform, longitudes, latitudes) end
 ---
 ---This function can draw up to 16384 visible characters at a time, and will currently throw an error if the string is too long.
 ---
+---@overload fun(self: lovr.Pass, text: string, position: lovr.Vec3, scale?: number, orientation: lovr.Quat, wrap?: number, halign?: lovr.HorizontalAlign, valign?: lovr.VerticalAlign)
+---@overload fun(self: lovr.Pass, text: string, transform: lovr.Mat4, wrap?: number, halign?: lovr.HorizontalAlign, valign?: lovr.VerticalAlign)
+---@overload fun(self: lovr.Pass, colortext: table, x?: number, y?: number, z?: number, scale?: number, angle?: number, ax?: number, ay?: number, az?: number, wrap?: number, halign?: lovr.HorizontalAlign, valign?: lovr.VerticalAlign)
+---@overload fun(self: lovr.Pass, colortext: table, position: lovr.Vec3, scale?: number, orientation: lovr.Quat, wrap?: number, halign?: lovr.HorizontalAlign, valign?: lovr.VerticalAlign)
 ---@overload fun(self: lovr.Pass, colortext: table, transform: lovr.Mat4, wrap?: number, halign?: lovr.HorizontalAlign, valign?: lovr.VerticalAlign)
 ---@param text string # The text to render.
----@param transform lovr.Mat4 # The transform of the text.  Can also be provided as position, 1-component scale, and rotation using a mix of `Vectors` or numbers.
+---@param x? number # The x coordinate of the text origin.
+---@param y? number # The y coordinate of the text origin.
+---@param z? number # The z coordinate of the text origin.
+---@param scale? number # The scale of the text (with the default pixel density, units are meters).
+---@param angle? number # The rotation of the text around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param wrap? number # The maximum width of each line in meters (before scale is applied).  When zero, the text will not wrap.
----@param halign? lovr.HorizontalAlign # The horizontal alignment.
----@param valign? lovr.VerticalAlign # The vertical alignment.
-function Pass:text(text, transform, wrap, halign, valign) end
+---@param halign? lovr.HorizontalAlign # The horizontal alignment relative to the text origin.
+---@param valign? lovr.VerticalAlign # The vertical alignment relative to the text origin.
+function Pass:text(text, x, y, z, scale, angle, ax, ay, az, wrap, halign, valign) end
 
 ---
 ---Starts a GPU measurement.
@@ -2369,16 +2682,37 @@ function Pass:tock(tally, slot) end
 ---### NOTE:
 ---The local origin is in the center of the torus, and the torus forms a circle around the local Z axis.
 ---
----@param transform lovr.Mat4 # The transform of the torus.  Can also be provided as position, scale, and rotation using a mix of `Vectors` or numbers.  When using numbers for the scale, 2 should be provided: one for the radius and one for the thickness.  When using a matrix or a vector for the scale, the X and Y components are the radius and the Z component is the thickness.
+---@overload fun(self: lovr.Pass, position: lovr.Vec3, scale: lovr.Vec3, orientation: lovr.Quat, tsegments?: number, psegments?: number)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4, tsegments?: number, psegments?: number)
+---@param x? number # The x coordinate of the center of the torus.
+---@param y? number # The y coordinate of the center of the torus.
+---@param z? number # The z coordinate of the center of the torus.
+---@param radius? number # The radius of the torus.
+---@param thickness? number # The thickness of the torus.
+---@param angle? number # The rotation of the torus around its rotation axis, in radians.
+---@param ax? number # The x component of the axis of rotation.
+---@param ay? number # The y component of the axis of rotation.
+---@param az? number # The z component of the axis of rotation.
 ---@param tsegments? number # The number of toroidal (circular) segments to render.
 ---@param psegments? number # The number of poloidal (tubular) segments to render.
-function Pass:torus(transform, tsegments, psegments) end
+function Pass:torus(x, y, z, radius, thickness, angle, ax, ay, az, tsegments, psegments) end
 
 ---
 ---Transforms the coordinate system.
 ---
----@param transform lovr.Mat4 # A matrix containing the transformation to apply to the coordinate system.  Can also be provided as a position, 3-component scale, and rotation, using a mix of `Vectors` or numbers.
-function Pass:transform(transform) end
+---@overload fun(self: lovr.Pass, translation: lovr.Vec3, scale: lovr.Vec3, rotation: lovr.Quat)
+---@overload fun(self: lovr.Pass, transform: lovr.Mat4)
+---@param x number # The x component of the translation.
+---@param y number # The y component of the translation.
+---@param z number # The z component of the translation.
+---@param sx number # The x component of the scale.
+---@param sy number # The y component of the scale.
+---@param sz number # The z component of the scale.
+---@param angle number # The amount to rotate the coordinate system by, in radians.
+---@param ax number # The x component of the axis of rotation.
+---@param ay number # The y component of the axis of rotation.
+---@param az number # The z component of the axis of rotation.
+function Pass:transform(x, y, z, sx, sy, sz, angle, ax, ay, az) end
 
 ---
 ---Translates the coordinate system.
@@ -2387,8 +2721,11 @@ function Pass:transform(transform) end
 ---### NOTE:
 ---Order matters when scaling, translating, and rotating the coordinate system.
 ---
----@param translation lovr.Vec3 # The translation to apply to the coordinate system.  Can also be provided as 3 numbers.
-function Pass:translate(translation) end
+---@overload fun(self: lovr.Pass, translation: lovr.Vec3)
+---@param x number # The x component of the translation.
+---@param y number # The y component of the translation.
+---@param z number # The z component of the translation.
+function Pass:translate(x, y, z) end
 
 ---
 ---Readbacks track the progress of an asynchronous read of a `Buffer`, `Texture`, or `Tally`.
@@ -3134,6 +3471,14 @@ function Texture:newView(type, layer, layerCount, mipmap, mipmapCount) end
 ---A 4x4 matrix containing sixteen 32-bit floats.
 ---
 ---| "mat4"
+---
+---Like u16, but 1-indexed.
+---
+---| "index16"
+---
+---Like u32, but 1-indexed.
+---
+---| "index32"
 
 ---
 ---Controls how `Sampler` objects smooth pixels in textures.
diff --git a/meta/3rd/lovr/library/lovr/headset.lua b/meta/3rd/lovr/library/lovr/headset.lua
index 0c26709ea..82c0e6c4a 100644
--- a/meta/3rd/lovr/library/lovr/headset.lua
+++ b/meta/3rd/lovr/library/lovr/headset.lua
@@ -33,6 +33,10 @@ function lovr.headset.animate(device, model) end
 ---
 ---Returns the current angular velocity of a device.
 ---
+---
+---### NOTE:
+---If the device isn't tracked, all zeroes will be returned.
+---
 ---@param device? lovr.Device # The device to get the velocity of.
 ---@return number x # The x component of the angular velocity.
 ---@return number y # The y component of the angular velocity.
@@ -61,20 +65,12 @@ function lovr.headset.getAxis(device, axis) end
 ---
 ---Returns the depth of the play area, in meters.
 ---
----
----### NOTE:
----This currently returns 0 on the Quest.
----
 ---@return number depth # The depth of the play area, in meters.
 function lovr.headset.getBoundsDepth() end
 
 ---
 ---Returns the size of the play area, in meters.
 ---
----
----### NOTE:
----This currently returns 0 on the Quest.
----
 ---@return number width # The width of the play area, in meters.
 ---@return number depth # The depth of the play area, in meters.
 function lovr.headset.getBoundsDimensions() end
@@ -89,10 +85,6 @@ function lovr.headset.getBoundsGeometry(t) end
 ---
 ---Returns the width of the play area, in meters.
 ---
----
----### NOTE:
----This currently returns 0 on the Quest.
----
 ---@return number width # The width of the play area, in meters.
 function lovr.headset.getBoundsWidth() end
 
@@ -423,6 +415,10 @@ function lovr.headset.getTime() end
 ---
 ---Returns the current linear velocity of a device, in meters per second.
 ---
+---
+---### NOTE:
+---If the device isn't tracked, all zeroes will be returned.
+---
 ---@param device? lovr.Device # The device to get the velocity of.
 ---@return number vx # The x component of the linear velocity.
 ---@return number vy # The y component of the linear velocity.
@@ -494,6 +490,18 @@ function lovr.headset.isDown(device, button) end
 ---@return boolean focused # Whether the application is focused.
 function lovr.headset.isFocused() end
 
+---
+---Returns whether passthrough is active.
+---
+---When passthrough is active, the real world will be rendered behind any content rendered by LÖVR, using the alpha channel to blend between the two.
+---
+---
+---### NOTE:
+---This feature is currently only supported on Oculus Quest devices.
+---
+---@return boolean active # Whether passthrough is active.
+function lovr.headset.isPassthroughEnabled() end
+
 ---
 ---Returns whether a button on a device is currently touched.
 ---
@@ -549,6 +557,19 @@ function lovr.headset.setClipDistance(near, far) end
 ---@return boolean success # Whether the display refresh rate was successfully set.
 function lovr.headset.setDisplayFrequency(frequency) end
 
+---
+---Sets whether passthrough is active.
+---
+---When passthrough is active, the real world will be rendered behind any content rendered by LÖVR, using the alpha channel to blend between the two.
+---
+---
+---### NOTE:
+---This feature is currently only supported on Oculus Quest devices.
+---
+---@param enable boolean # Whether passthrough should be enabled.
+---@return boolean success # Whether the passthrough state was set successfully.
+function lovr.headset.setPassthroughEnabled(enable) end
+
 ---
 ---Starts the headset session.
 ---
@@ -612,12 +633,26 @@ function lovr.headset.wasReleased(device, button) end
 ---
 ---Different types of input devices supported by the `lovr.headset` module.
 ---
+---
+---### NOTE:
+---The difference between `hand/left` and `hand/left/point` is the first represents an object held in the hand, whereas the second represents the laser pointer used to aim.
+---
+---Drawing a controller model would use `hand/left`, whereas drawing a pointer or aiming would use `hand/left/point`.
+---
 ---@alias lovr.Device
 ---
 ---The headset.
 ---
 ---| "head"
 ---
+---A shorthand for hand/left.
+---
+---| "left"
+---
+---A shorthand for hand/right.
+---
+---| "right"
+---
 ---The left controller.
 ---
 ---| "hand/left"
@@ -626,13 +661,13 @@ function lovr.headset.wasReleased(device, button) end
 ---
 ---| "hand/right"
 ---
----A shorthand for hand/left.
+---The left controller pointer (pose only).
 ---
----| "left"
+---| "hand/left/point"
 ---
----A shorthand for hand/right.
+---The right controller pointer (pose only).
 ---
----| "right"
+---| "hand/right/point"
 ---
 ---A device tracking the left elbow.
 ---
diff --git a/meta/3rd/lovr/library/lovr/math.lua b/meta/3rd/lovr/library/lovr/math.lua
index 907f4850f..163e59547 100644
--- a/meta/3rd/lovr/library/lovr/math.lua
+++ b/meta/3rd/lovr/library/lovr/math.lua
@@ -50,7 +50,7 @@ function lovr.math.linearToGamma(lr, lg, lb) end
 ---
 ---This function takes the same arguments as `Mat4:set`.
 ---
----@overload fun(n: lovr.mat4):lovr.Mat4
+---@overload fun(n: lovr.Mat4):lovr.Mat4
 ---@overload fun(position?: lovr.Vec3, scale?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
 ---@overload fun(position?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
 ---@overload fun(...):lovr.Mat4
@@ -61,6 +61,7 @@ function lovr.math.mat4() end
 ---
 ---Creates a new `Curve` from a list of control points.
 ---
+---@overload fun(v: lovr.Vec3, ...):lovr.Curve
 ---@overload fun(points: table):lovr.Curve
 ---@overload fun(n: number):lovr.Curve
 ---@param x number # The x coordinate of the first control point.
@@ -75,7 +76,7 @@ function lovr.math.newCurve(x, y, z, ...) end
 ---
 ---This function takes the same arguments as `Mat4:set`.
 ---
----@overload fun(n: lovr.mat4):lovr.Mat4
+---@overload fun(n: lovr.Mat4):lovr.Mat4
 ---@overload fun(position?: lovr.Vec3, scale?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
 ---@overload fun(position?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
 ---@overload fun(...):lovr.Mat4
@@ -88,17 +89,17 @@ function lovr.math.newMat4() end
 ---
 ---This function takes the same arguments as `Quat:set`.
 ---
----@overload fun(r: lovr.quat):lovr.quat
----@overload fun(v: lovr.vec3):lovr.quat
----@overload fun(v: lovr.vec3, u: lovr.vec3):lovr.quat
----@overload fun(m: lovr.mat4):lovr.quat
----@overload fun():lovr.quat
+---@overload fun(r: lovr.Quat):lovr.Quat
+---@overload fun(v: lovr.Vec3):lovr.Quat
+---@overload fun(v: lovr.Vec3, u: lovr.Vec3):lovr.Quat
+---@overload fun(m: lovr.Mat4):lovr.Quat
+---@overload fun():lovr.Quat
 ---@param angle? number # An angle to use for the rotation, in radians.
 ---@param ax? number # The x component of the axis of rotation.
 ---@param ay? number # The y component of the axis of rotation.
 ---@param az? number # The z component of the axis of rotation.
 ---@param raw? boolean # Whether the components should be interpreted as raw `(x, y, z, w)` components.
----@return lovr.quat q # The new quaternion.
+---@return lovr.Quat q # The new quaternion.
 function lovr.math.newQuat(angle, ax, ay, az, raw) end
 
 ---
@@ -163,17 +164,17 @@ function lovr.math.noise(x) end
 ---
 ---This function takes the same arguments as `Quat:set`.
 ---
----@overload fun(r: lovr.quat):lovr.quat
----@overload fun(v: lovr.vec3):lovr.quat
----@overload fun(v: lovr.vec3, u: lovr.vec3):lovr.quat
----@overload fun(m: lovr.mat4):lovr.quat
----@overload fun():lovr.quat
+---@overload fun(r: lovr.Quat):lovr.Quat
+---@overload fun(v: lovr.Vec3):lovr.Quat
+---@overload fun(v: lovr.Vec3, u: lovr.Vec3):lovr.Quat
+---@overload fun(m: lovr.Mat4):lovr.Quat
+---@overload fun():lovr.Quat
 ---@param angle? number # An angle to use for the rotation, in radians.
 ---@param ax? number # The x component of the axis of rotation.
 ---@param ay? number # The y component of the axis of rotation.
 ---@param az? number # The z component of the axis of rotation.
 ---@param raw? boolean # Whether the components should be interpreted as raw `(x, y, z, w)` components.
----@return lovr.quat q # The new quaternion.
+---@return lovr.Quat q # The new quaternion.
 function lovr.math.quat(angle, ax, ay, az, raw) end
 
 ---
@@ -399,19 +400,19 @@ function Mat4:equals(n) end
 ---@param down number # The bottom half-angle of the projection, in radians.
 ---@param near number # The near plane of the projection.
 ---@param far? number # The far plane.  Zero is a special value that will set an infinite far plane with a reversed Z range, which improves depth buffer precision and is the default.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:fov(left, right, up, down, near, far) end
 
 ---
 ---Resets the matrix to the identity, effectively setting its translation to zero, its scale to 1, and clearing any rotation.
 ---
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:identity() end
 
 ---
 ---Inverts the matrix, causing it to represent the opposite of its old transform.
 ---
----@return lovr.Mat4 m # The original matrix, with its values inverted.
+---@return lovr.Mat4 self # The inverted matrix.
 function Mat4:invert() end
 
 ---
@@ -424,7 +425,7 @@ function Mat4:invert() end
 ---@param from lovr.Vec3 # The position of the viewer.
 ---@param to lovr.Vec3 # The position of the target.
 ---@param up? lovr.Vec3 # The up vector of the viewer.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:lookAt(from, to, up) end
 
 ---
@@ -441,7 +442,7 @@ function Mat4:lookAt(from, to, up) end
 ---@overload fun(self: lovr.Mat4, v3: lovr.Vec3):lovr.Vec3
 ---@overload fun(self: lovr.Mat4, v4: lovr.Vec4):lovr.Vec4
 ---@param n lovr.Mat4 # The matrix.
----@return lovr.Mat4 m # The original matrix, containing the result.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:mul(n) end
 
 ---
@@ -456,7 +457,7 @@ function Mat4:mul(n) end
 ---@param top number # The top edge of the projection.
 ---@param near number # The position of the near clipping plane.
 ---@param far number # The position of the far clipping plane.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:orthographic(left, right, bottom, top, near, far) end
 
 ---
@@ -468,15 +469,23 @@ function Mat4:orthographic(left, right, bottom, top, near, far) end
 ---@param aspect number # The horizontal aspect ratio of the projection (width / height).
 ---@param near number # The near plane.
 ---@param far? number # The far plane.  Zero is a special value that will set an infinite far plane with a reversed Z range, which improves depth buffer precision and is the default.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:perspective(fov, aspect, near, far) end
 
+---
+---Turns the matrix into a reflection matrix that transforms values as though they were reflected across a plane.
+---
+---@param position lovr.Vec3 # The position of the plane.
+---@param normal lovr.Vec3 # The normal vector of the plane.
+---@return lovr.Mat4 self # The reflected matrix.
+function Mat4:reflect(position, normal) end
+
 ---
 ---Rotates the matrix using a quaternion or an angle/axis rotation.
 ---
 ---@overload fun(self: lovr.Mat4, angle: number, ax?: number, ay?: number, az?: number):lovr.Mat4
 ---@param q lovr.Quat # The rotation to apply to the matrix.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The rotated matrix.
 function Mat4:rotate(q) end
 
 ---
@@ -484,15 +493,17 @@ function Mat4:rotate(q) end
 ---
 ---@overload fun(self: lovr.Mat4, sx: number, sy?: number, sz?: number):lovr.Mat4
 ---@param scale lovr.Vec3 # The 3D scale to apply.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:scale(scale) end
 
 ---
 ---Sets the components of the matrix from separate position, rotation, and scale arguments or an existing matrix.
 ---
----@overload fun(self: lovr.Mat4, n: lovr.mat4):lovr.Mat4
----@overload fun(self: lovr.Mat4, position?: lovr.Vec3, scale?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
----@overload fun(self: lovr.Mat4, position?: lovr.Vec3, rotation?: lovr.Quat):lovr.Mat4
+---@overload fun(self: lovr.Mat4, n: lovr.Mat4):lovr.Mat4
+---@overload fun(self: lovr.Mat4, x: number, y: number, z: number, sx: number, sy: number, sz: number, angle: number, ax: number, ay: number, az: number):lovr.Mat4
+---@overload fun(self: lovr.Mat4, x: number, y: number, z: number, angle: number, ax: number, ay: number, az: number):lovr.Mat4
+---@overload fun(self: lovr.Mat4, position: lovr.Vec3, scale: lovr.Vec3, rotation: lovr.Quat):lovr.Mat4
+---@overload fun(self: lovr.Mat4, position: lovr.Vec3, rotation: lovr.Quat):lovr.Mat4
 ---@overload fun(self: lovr.Mat4, ...):lovr.Mat4
 ---@overload fun(self: lovr.Mat4, d: number):lovr.Mat4
 ---@return lovr.Mat4 m # The input matrix.
@@ -508,7 +519,7 @@ function Mat4:set() end
 ---@param from lovr.Vec3 # The position of the viewer.
 ---@param to lovr.Vec3 # The position of the target.
 ---@param up? lovr.Vec3 # The up vector of the viewer.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The modified matrix.
 function Mat4:target(from, to, up) end
 
 ---
@@ -516,19 +527,19 @@ function Mat4:target(from, to, up) end
 ---
 ---@overload fun(self: lovr.Mat4, x: number, y: number, z: number):lovr.Mat4
 ---@param v lovr.Vec3 # The translation vector.
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The translated matrix.
 function Mat4:translate(v) end
 
 ---
 ---Transposes the matrix, mirroring its values along the diagonal.
 ---
----@return lovr.Mat4 m # The original matrix.
+---@return lovr.Mat4 self # The transposed matrix.
 function Mat4:transpose() end
 
 ---
 ---Returns the components of matrix, either as 10 separated numbers representing the position, scale, and rotation, or as 16 raw numbers representing the individual components of the matrix in column-major order.
 ---
----@param raw boolean # Whether to return the 16 raw components.
+---@param raw? boolean # Whether to return the 16 raw components.
 function Mat4:unpack(raw) end
 
 ---
@@ -544,7 +555,7 @@ local Quat = {}
 ---
 ---It negates the (x, y, z) components of the quaternion.
 ---
----@return lovr.Quat q # The original quaternion.
+---@return lovr.Quat self # The inverted quaternion.
 function Quat:conjugate() end
 
 ---
@@ -573,9 +584,9 @@ function Quat:length() end
 ---
 ---If the value is a vector, then the input vector is rotated by the quaternion and returned.
 ---
----@overload fun(self: lovr.Quat, v3: lovr.vec3):lovr.vec3
----@param r lovr.quat # A quaternion to combine with the original.
----@return lovr.quat q # The original quaternion.
+---@overload fun(self: lovr.Quat, v3: lovr.Vec3):lovr.Vec3
+---@param r lovr.Quat # A quaternion to combine with the original.
+---@return lovr.Quat self # The modified quaternion.
 function Quat:mul(r) end
 
 ---
@@ -587,7 +598,7 @@ function Quat:mul(r) end
 ---
 ---Try normalizing a quaternion if some of the calculations aren't working quite right!
 ---
----@return lovr.Quat q # The original quaternion.
+---@return lovr.Quat self # The normalized quaternion.
 function Quat:normalize() end
 
 ---
@@ -604,17 +615,17 @@ function Quat:normalize() end
 ---  two vectors.
 ---- A matrix can be passed in to extract the rotation of the matrix into a quaternion.
 ---
----@overload fun(self: lovr.Quat, r: lovr.quat):lovr.quat
----@overload fun(self: lovr.Quat, v: lovr.vec3):lovr.quat
----@overload fun(self: lovr.Quat, v: lovr.vec3, u: lovr.vec3):lovr.quat
----@overload fun(self: lovr.Quat, m: lovr.mat4):lovr.quat
----@overload fun(self: lovr.Quat):lovr.quat
+---@overload fun(self: lovr.Quat, r: lovr.Quat):lovr.Quat
+---@overload fun(self: lovr.Quat, v: lovr.Vec3):lovr.Quat
+---@overload fun(self: lovr.Quat, v: lovr.Vec3, u: lovr.Vec3):lovr.Quat
+---@overload fun(self: lovr.Quat, m: lovr.Mat4):lovr.Quat
+---@overload fun(self: lovr.Quat):lovr.Quat
 ---@param angle? number # The angle to use for the rotation, in radians.
 ---@param ax? number # The x component of the axis of rotation.
 ---@param ay? number # The y component of the axis of rotation.
 ---@param az? number # The z component of the axis of rotation.
 ---@param raw? boolean # Whether the components should be interpreted as raw `(x, y, z, w)` components.
----@return lovr.quat q # The original quaternion.
+---@return lovr.Quat self # The modified quaternion.
 function Quat:set(angle, ax, ay, az, raw) end
 
 ---
@@ -628,7 +639,7 @@ function Quat:set(angle, ax, ay, az, raw) end
 ---
 ---@param r lovr.Quat # The quaternion to slerp towards.
 ---@param t number # The lerping parameter.
----@return lovr.Quat q # The original quaternion, containing the new lerped values.
+---@return lovr.Quat self # The modified quaternion, containing the new lerped values.
 function Quat:slerp(r, t) end
 
 ---
@@ -722,7 +733,7 @@ local Vec2 = {}
 ---
 ---@overload fun(self: lovr.Vec2, x: number, y?: number):lovr.Vec2
 ---@param u lovr.Vec2 # The other vector.
----@return lovr.Vec2 v # The original vector.
+---@return lovr.Vec2 self # The modified vector.
 function Vec2:add(u) end
 
 ---
@@ -752,7 +763,7 @@ function Vec2:distance(u) end
 ---
 ---@overload fun(self: lovr.Vec2, x: number, y?: number):lovr.Vec2
 ---@param u lovr.Vec2 # The other vector to divide the components by.
----@return lovr.Vec2 v # The original vector.
+---@return lovr.Vec2 self # The modified vector.
 function Vec2:div(u) end
 
 ---
@@ -803,7 +814,7 @@ function Vec2:length() end
 ---@overload fun(self: lovr.Vec2, x: number, y: number, t: number):lovr.Vec2
 ---@param u lovr.Vec2 # The vector to lerp towards.
 ---@param t number # The lerping parameter.
----@return lovr.Vec2 v # The original vector, containing the new lerped values.
+---@return lovr.Vec2 self # The interpolated vector.
 function Vec2:lerp(u, t) end
 
 ---
@@ -811,13 +822,13 @@ function Vec2:lerp(u, t) end
 ---
 ---@overload fun(self: lovr.Vec2, x: number, y?: number):lovr.Vec2
 ---@param u lovr.Vec2 # The other vector to multiply the components by.
----@return lovr.Vec2 v # The original vector.
+---@return lovr.Vec2 self # The modified vector.
 function Vec2:mul(u) end
 
 ---
 ---Adjusts the values in the vector so that its direction stays the same but its length becomes 1.
 ---
----@return lovr.Vec2 v # The original vector.
+---@return lovr.Vec2 self # The normalized vector.
 function Vec2:normalize() end
 
 ---
@@ -834,7 +845,7 @@ function Vec2:set(x, y) end
 ---
 ---@overload fun(self: lovr.Vec2, x: number, y?: number):lovr.Vec2
 ---@param u lovr.Vec2 # The other vector.
----@return lovr.Vec2 v # The original vector.
+---@return lovr.Vec2 self # The modified vector.
 function Vec2:sub(u) end
 
 ---
@@ -855,7 +866,7 @@ local Vec3 = {}
 ---
 ---@overload fun(self: lovr.Vec3, x: number, y?: number, z?: number):lovr.Vec3
 ---@param u lovr.Vec3 # The other vector.
----@return lovr.Vec3 v # The original vector.
+---@return lovr.Vec3 self # The modified vector.
 function Vec3:add(u) end
 
 ---
@@ -883,7 +894,7 @@ function Vec3:angle(u) end
 ---
 ---@overload fun(self: lovr.Vec3, x: number, y: number, z: number):lovr.Vec3
 ---@param u lovr.Vec3 # The vector to compute the cross product with.
----@return lovr.Vec3 v # The original vector, with the cross product as its values.
+---@return lovr.Vec3 self # The modified vector.
 function Vec3:cross(u) end
 
 ---
@@ -899,7 +910,7 @@ function Vec3:distance(u) end
 ---
 ---@overload fun(self: lovr.Vec3, x: number, y?: number, z?: number):lovr.Vec3
 ---@param u lovr.Vec3 # The other vector to divide the components by.
----@return lovr.Vec3 v # The original vector.
+---@return lovr.Vec3 self # The modified vector.
 function Vec3:div(u) end
 
 ---
@@ -950,7 +961,7 @@ function Vec3:length() end
 ---@overload fun(self: lovr.Vec3, x: number, y: number, z: number, t: number):lovr.Vec3
 ---@param u lovr.Vec3 # The vector to lerp towards.
 ---@param t number # The lerping parameter.
----@return lovr.Vec3 v # The original vector, containing the new lerped values.
+---@return lovr.Vec3 self # The interpolated vector.
 function Vec3:lerp(u, t) end
 
 ---
@@ -958,19 +969,20 @@ function Vec3:lerp(u, t) end
 ---
 ---@overload fun(self: lovr.Vec3, x: number, y?: number, z?: number):lovr.Vec3
 ---@param u lovr.Vec3 # The other vector to multiply the components by.
----@return lovr.Vec3 v # The original vector.
+---@return lovr.Vec3 self # The modified vector.
 function Vec3:mul(u) end
 
 ---
 ---Adjusts the values in the vector so that its direction stays the same but its length becomes 1.
 ---
----@return lovr.Vec3 v # The original vector.
+---@return lovr.Vec3 self # The normalized vector.
 function Vec3:normalize() end
 
 ---
 ---Sets the components of the vector, either from numbers or an existing vector.
 ---
 ---@overload fun(self: lovr.Vec3, u: lovr.Vec3):lovr.Vec3
+---@overload fun(self: lovr.Vec3, q: lovr.Quat):lovr.Vec3
 ---@overload fun(self: lovr.Vec3, m: lovr.Mat4):lovr.Vec3
 ---@param x? number # The new x value of the vector.
 ---@param y? number # The new y value of the vector.
@@ -983,7 +995,7 @@ function Vec3:set(x, y, z) end
 ---
 ---@overload fun(self: lovr.Vec3, x: number, y?: number, z?: number):lovr.Vec3
 ---@param u lovr.Vec3 # The other vector.
----@return lovr.Vec3 v # The original vector.
+---@return lovr.Vec3 self # The modified vector.
 function Vec3:sub(u) end
 
 ---
@@ -1005,7 +1017,7 @@ local Vec4 = {}
 ---
 ---@overload fun(self: lovr.Vec4, x: number, y?: number, z?: number, w?: number):lovr.Vec4
 ---@param u lovr.Vec4 # The other vector.
----@return lovr.Vec4 v # The original vector.
+---@return lovr.Vec4 self # The modified vector.
 function Vec4:add(u) end
 
 ---
@@ -1035,7 +1047,7 @@ function Vec4:distance(u) end
 ---
 ---@overload fun(self: lovr.Vec4, x: number, y?: number, z?: number, w?: number):lovr.Vec4
 ---@param u lovr.Vec4 # The other vector to divide the components by.
----@return lovr.Vec4 v # The original vector.
+---@return lovr.Vec4 self # The modified vector.
 function Vec4:div(u) end
 
 ---
@@ -1086,7 +1098,7 @@ function Vec4:length() end
 ---@overload fun(self: lovr.Vec4, x: number, y: number, z: number, w: number, t: number):lovr.Vec4
 ---@param u lovr.Vec4 # The vector to lerp towards.
 ---@param t number # The lerping parameter.
----@return lovr.Vec4 v # The original vector, containing the new lerped values.
+---@return lovr.Vec4 self # The interpolated vector.
 function Vec4:lerp(u, t) end
 
 ---
@@ -1094,13 +1106,13 @@ function Vec4:lerp(u, t) end
 ---
 ---@overload fun(self: lovr.Vec4, x: number, y?: number, z?: number, w?: number):lovr.Vec4
 ---@param u lovr.Vec4 # The other vector to multiply the components by.
----@return lovr.Vec4 v # The original vector.
+---@return lovr.Vec4 self # The modified vector.
 function Vec4:mul(u) end
 
 ---
 ---Adjusts the values in the vector so that its direction stays the same but its length becomes 1.
 ---
----@return lovr.Vec4 v # The original vector.
+---@return lovr.Vec4 self # The normalized vector.
 function Vec4:normalize() end
 
 ---
@@ -1119,7 +1131,7 @@ function Vec4:set(x, y, z, w) end
 ---
 ---@overload fun(self: lovr.Vec4, x: number, y?: number, z?: number, w?: number):lovr.Vec4
 ---@param u lovr.Vec4 # The other vector.
----@return lovr.Vec4 v # The original vector.
+---@return lovr.Vec4 self # The modified vector.
 function Vec4:sub(u) end
 
 ---
@@ -1165,11 +1177,14 @@ function Vec4:unpack() end
 ---
 ---    local position = lovr.math.vec3(x, y, z)
 ---
----As a further shorthand, these vector constructors are placed on the global scope.
+---As a shortcut, vector constructors are placed on the global scope.
 ---
----If you prefer to keep the global scope clean, this can be configured using the `t.math.globals` flag in `lovr.conf`.
+---The uppercase name of the vector is a function that will create a permanent vector, and the lowercase name will create a temporary vector.
+---
+---This can be disabled using the `t.math.globals` option in `lovr.conf`.
 ---
 ---    local position = vec3(x1, y1, z1) + vec3(x2, y2, z2)
+---    local transform = Mat4()
 ---
 ---Temporary vectors, with all their speed, come with an important restriction: they can only be used during the frame in which they were created.
 ---
@@ -1178,7 +1193,7 @@ function Vec4:unpack() end
 ---    local position = vec3(1, 2, 3)
 ---
 ---    function lovr.update(dt)
----      -- Reusing a temporary vector across frames will error:
+---      -- Reusing the temporary 'position' vector across frames will error:
 ---      position:add(vec3(dt))
 ---    end
 ---
@@ -1229,5 +1244,25 @@ function Vec4:unpack() end
 ---
 ---Similarly, matrices support raw unpacking as well as decomposition into translation/scale/rotation values.
 ---
+---### Vector Constants
+---
+---The following vector constants are available.
+---
+---They return new temporary vectors each time they are used:
+---
+---- `vec2.zero` (0, 0)
+---- `vec2.one` (1, 1)
+---- `vec3.zero` (0, 0, 0)
+---- `vec3.one` (1, 1, 1)
+---- `vec3.left` (-1, 0, 0)
+---- `vec3.right` (1, 0, 0)
+---- `vec3.up` (0, 1, 0)
+---- `vec3.down` (0, -1, 0)
+---- `vec3.back` (0, 0, 1)
+---- `vec3.forward` (0, 0, -1)
+---- `vec4.zero` (0, 0, 0, 0)
+---- `vec4.one` (1, 1, 1, 1)
+---- `quat.identity` (0, 0, 0, 1)
+---
 ---@class lovr.Vectors
 local Vectors = {}
diff --git a/meta/3rd/lovr/library/lovr/physics.lua b/meta/3rd/lovr/library/lovr/physics.lua
index 661b49cdc..b699fc81f 100644
--- a/meta/3rd/lovr/library/lovr/physics.lua
+++ b/meta/3rd/lovr/library/lovr/physics.lua
@@ -15,6 +15,7 @@ lovr.physics = {}
 ---
 ---It tries to keep the distance between the colliders and the anchor position the same, but does not constrain the angle between them.
 ---
+---@overload fun(colliderA: lovr.Collider, colliderB: lovr.Collider, anchor: lovr.Vec3):lovr.BallJoint
 ---@param colliderA lovr.Collider # The first collider to attach the Joint to.
 ---@param colliderB lovr.Collider # The second collider to attach the Joint to.
 ---@param x number # The x position of the joint anchor point, in world coordinates.
@@ -73,6 +74,7 @@ function lovr.physics.newCylinderShape(radius, length) end
 ---
 ---The joint allows for rotation on the anchor points.
 ---
+---@overload fun(colliderA: lovr.Collider, colliderB: lovr.Collider, first: lovr.Vec3, second: lovr.Vec3):lovr.DistanceJoint
 ---@param colliderA lovr.Collider # The first collider to attach the Joint to.
 ---@param colliderB lovr.Collider # The second collider to attach the Joint to.
 ---@param x1 number # The x position of the first anchor point, in world coordinates.
@@ -91,17 +93,31 @@ function lovr.physics.newDistanceJoint(colliderA, colliderB, x1, y1, z1, x2, y2,
 ---### NOTE:
 ---A hinge joint constrains two colliders to allow rotation only around the hinge's axis.
 ---
+---@overload fun(colliderA: lovr.Collider, colliderB: lovr.Collider, anchor: lovr.Vec3, axis: lovr.Vec3):lovr.HingeJoint
 ---@param colliderA lovr.Collider # The first collider to attach the Joint to.
 ---@param colliderB lovr.Collider # The second collider to attach the Joint to.
 ---@param x number # The x position of the hinge anchor, in world coordinates.
 ---@param y number # The y position of the hinge anchor, in world coordinates.
 ---@param z number # The z position of the hinge anchor, in world coordinates.
----@param ax number # The x component of the hinge axis.
----@param ay number # The y component of the hinge axis.
----@param az number # The z component of the hinge axis.
+---@param ax number # The x component of the hinge axis direction.
+---@param ay number # The y component of the hinge axis direction.
+---@param az number # The z component of the hinge axis direction.
 ---@return lovr.HingeJoint hinge # The new HingeJoint.
 function lovr.physics.newHingeJoint(colliderA, colliderB, x, y, z, ax, ay, az) end
 
+---
+---Creates a new MeshShape.
+---
+---
+---### NOTE:
+---A Shape can be attached to a Collider using `Collider:addShape`.
+---
+---@overload fun(model: lovr.Model):lovr.MeshShape
+---@param vertices table # The table of vertices in the mesh.  Each vertex is a table with 3 numbers.
+---@param indices table # A table of triangle indices representing how the vertices are connected in the Mesh.
+---@return lovr.MeshShape mesh # The new MeshShape.
+function lovr.physics.newMeshShape(vertices, indices) end
+
 ---
 ---Creates a new SliderJoint.
 ---
@@ -109,6 +125,7 @@ function lovr.physics.newHingeJoint(colliderA, colliderB, x, y, z, ax, ay, az) e
 ---### NOTE:
 ---A slider joint constrains two colliders to only allow movement along the slider's axis.
 ---
+---@overload fun(colliderA: lovr.Collider, colliderB: lovr.Collider, axis: lovr.Vec3):lovr.SliderJoint
 ---@param colliderA lovr.Collider # The first collider to attach the Joint to.
 ---@param colliderB lovr.Collider # The second collider to attach the Joint to.
 ---@param ax number # The x component of the slider axis.
@@ -128,6 +145,19 @@ function lovr.physics.newSliderJoint(colliderA, colliderB, ax, ay, az) end
 ---@return lovr.SphereShape sphere # The new SphereShape.
 function lovr.physics.newSphereShape(radius) end
 
+---
+---Creates a new TerrainShape.
+---
+---
+---### NOTE:
+---A Shape can be attached to a Collider using `Collider:addShape`. For immobile terrain use the `Collider:setKinematic`.
+---
+---@overload fun(scale: number, heightmap: lovr.Image, stretch?: number):lovr.TerrainShape
+---@overload fun(scale: number, callback: function, samples?: number):lovr.TerrainShape
+---@param scale number # The width and depth of the terrain, in meters.
+---@return lovr.TerrainShape terrain # The new TerrainShape.
+function lovr.physics.newTerrainShape(scale) end
+
 ---
 ---Creates a new physics World, which tracks the overall physics simulation, holds collider objects, and resolves collisions between them.
 ---
@@ -185,6 +215,7 @@ function BallJoint:getTightness() end
 ---
 ---Sets a new anchor point for the BallJoint.
 ---
+---@overload fun(self: lovr.BallJoint, anchor: lovr.Vec3)
 ---@param x number # The x coordinate of the anchor point, in world coordinates.
 ---@param y number # The y coordinate of the anchor point, in world coordinates.
 ---@param z number # The z coordinate of the anchor point, in world coordinates.
@@ -282,6 +313,8 @@ function Collider:addShape(shape) end
 ---If the Collider is asleep, it will need to be woken up with `Collider:setAwake` for this function to have any affect.
 ---
 ---@overload fun(self: lovr.Collider, x: number, y: number, z: number, px: number, py: number, pz: number)
+---@overload fun(self: lovr.Collider, force: lovr.Vec3)
+---@overload fun(self: lovr.Collider, force: lovr.Vec3, position: lovr.Vec3)
 ---@param x number # The x component of the force to apply.
 ---@param y number # The y component of the force to apply.
 ---@param z number # The z component of the force to apply.
@@ -294,6 +327,7 @@ function Collider:applyForce(x, y, z) end
 ---### NOTE:
 ---If the Collider is asleep, it will need to be woken up with `Collider:setAwake` for this function to have any effect.
 ---
+---@overload fun(self: lovr.Collider, torque: lovr.Vec3)
 ---@param x number # The x component of the torque.
 ---@param y number # The y component of the torque.
 ---@param z number # The z component of the torque.
@@ -388,6 +422,7 @@ function Collider:getLinearVelocity() end
 ---
 ---Returns the linear velocity of a point relative to the Collider.
 ---
+---@overload fun(self: lovr.Collider, point: number):number, number, number
 ---@param x number # The x coordinate.
 ---@param y number # The y coordinate.
 ---@param z number # The z coordinate.
@@ -399,6 +434,7 @@ function Collider:getLinearVelocityFromLocalPoint(x, y, z) end
 ---
 ---Returns the linear velocity of a point on the Collider specified in world space.
 ---
+---@overload fun(self: lovr.Collider, point: lovr.Vec3):number, number, number
 ---@param x number # The x coordinate in world space.
 ---@param y number # The y coordinate in world space.
 ---@param z number # The z coordinate in world space.
@@ -418,6 +454,7 @@ function Collider:getLocalCenter() end
 ---
 ---Converts a point from world coordinates into local coordinates relative to the Collider.
 ---
+---@overload fun(self: lovr.Collider, point: lovr.Vec3):number, number, number
 ---@param wx number # The x coordinate of the world point.
 ---@param wy number # The y coordinate of the world point.
 ---@param wz number # The z coordinate of the world point.
@@ -429,6 +466,7 @@ function Collider:getLocalPoint(wx, wy, wz) end
 ---
 ---Converts a direction vector from world space to local space.
 ---
+---@overload fun(self: lovr.Collider, vector: lovr.Vec3):number, number, number
 ---@param wx number # The x component of the world vector.
 ---@param wy number # The y component of the world vector.
 ---@param wz number # The z component of the world vector.
@@ -533,6 +571,7 @@ function Collider:getWorld() end
 ---
 ---Convert a point relative to the collider to a point in world coordinates.
 ---
+---@overload fun(self: lovr.Collider, point: lovr.Vec3):number, number, number
 ---@param x number # The x position of the point.
 ---@param y number # The y position of the point.
 ---@param z number # The z position of the point.
@@ -544,6 +583,7 @@ function Collider:getWorldPoint(x, y, z) end
 ---
 ---Converts a direction vector from local space to world space.
 ---
+---@overload fun(self: lovr.Collider, vector: lovr.Vec3):number, number, number
 ---@param x number # The x coordinate of the local vector.
 ---@param y number # The y coordinate of the local vector.
 ---@param z number # The z coordinate of the local vector.
@@ -616,6 +656,7 @@ function Collider:setAngularDamping(damping, threshold) end
 ---
 ---Sets the angular velocity of the Collider.
 ---
+---@overload fun(self: lovr.Collider, velocity: lovr.Vec3)
 ---@param vx number # The x component of the angular velocity.
 ---@param vy number # The y component of the angular velocity.
 ---@param vz number # The z component of the angular velocity.
@@ -677,6 +718,7 @@ function Collider:setLinearDamping(damping, threshold) end
 ---
 ---Usually it's preferred to use `Collider:applyForce` to change velocity since instantaneous velocity changes can lead to weird glitches.
 ---
+---@overload fun(self: lovr.Collider, velocity: lovr.Vec3)
 ---@param vx number # The x velocity of the Collider, in meters per second.
 ---@param vy number # The y velocity of the Collider, in meters per second.
 ---@param vz number # The z velocity of the Collider, in meters per second.
@@ -701,6 +743,7 @@ function Collider:setMassData(cx, cy, cz, mass, inertia) end
 ---
 ---Sets the orientation of the Collider in angle/axis representation.
 ---
+---@overload fun(self: lovr.Collider, orientation: lovr.Quat)
 ---@param angle number # The number of radians the Collider is rotated around its axis of rotation.
 ---@param ax number # The x component of the axis of rotation.
 ---@param ay number # The y component of the axis of rotation.
@@ -710,6 +753,7 @@ function Collider:setOrientation(angle, ax, ay, az) end
 ---
 ---Sets the position and orientation of the Collider.
 ---
+---@overload fun(self: lovr.Collider, position: lovr.Vec3, orientation: lovr.Quat)
 ---@param x number # The x position of the Collider, in meters.
 ---@param y number # The y position of the Collider, in meters.
 ---@param z number # The z position of the Collider, in meters.
@@ -722,6 +766,7 @@ function Collider:setPose(x, y, z, angle, ax, ay, az) end
 ---
 ---Sets the position of the Collider.
 ---
+---@overload fun(self: lovr.Collider, position: lovr.Vec3)
 ---@param x number # The x position of the Collider, in meters.
 ---@param y number # The y position of the Collider, in meters.
 ---@param z number # The z position of the Collider, in meters.
@@ -847,6 +892,7 @@ function DistanceJoint:getTightness() end
 ---
 ---Sets the anchor points of the DistanceJoint.
 ---
+---@overload fun(self: lovr.DistanceJoint, first: lovr.Vec3, second: lovr.Vec3)
 ---@param x1 number # The x coordinate of the first anchor point, in world coordinates.
 ---@param y1 number # The y coordinate of the first anchor point, in world coordinates.
 ---@param z1 number # The z coordinate of the first anchor point, in world coordinates.
@@ -940,6 +986,7 @@ function HingeJoint:getUpperLimit() end
 ---
 ---Sets a new anchor point for the HingeJoint.
 ---
+---@overload fun(self: lovr.HingeJoint, anchor: lovr.Vec3)
 ---@param x number # The x coordinate of the anchor point, in world coordinates.
 ---@param y number # The y coordinate of the anchor point, in world coordinates.
 ---@param z number # The z coordinate of the anchor point, in world coordinates.
@@ -948,6 +995,7 @@ function HingeJoint:setAnchor(x, y, z) end
 ---
 ---Sets the axis of the hinge.
 ---
+---@overload fun(self: lovr.HingeJoint, axis: lovr.Vec3)
 ---@param x number # The x component of the axis.
 ---@param y number # The y component of the axis.
 ---@param z number # The z component of the axis.
@@ -1032,6 +1080,12 @@ function Joint:setEnabled(enabled) end
 ---@param data any # The custom value associated with the Joint.
 function Joint:setUserData(data) end
 
+---
+---A type of `Shape` that can be used for triangle meshes.
+---
+---@class lovr.MeshShape
+local MeshShape = {}
+
 ---
 ---A Shape is a physics object that can be attached to colliders to define their shape.
 ---
@@ -1147,6 +1201,7 @@ function Shape:setEnabled(enabled) end
 ---### NOTE:
 ---If the Shape isn't attached to a Collider, this will error.
 ---
+---@overload fun(self: lovr.Shape, orientation: lovr.Quat)
 ---@param angle number # The number of radians the Shape is rotated.
 ---@param ax number # The x component of the rotation axis.
 ---@param ay number # The y component of the rotation axis.
@@ -1160,6 +1215,7 @@ function Shape:setOrientation(angle, ax, ay, az) end
 ---### NOTE:
 ---If the Shape isn't attached to a Collider, this will error.
 ---
+---@overload fun(self: lovr.Shape, position: lovr.Vec3)
 ---@param x number # The x offset.
 ---@param y number # The y offset.
 ---@param z number # The z offset.
@@ -1168,7 +1224,7 @@ function Shape:setPosition(x, y, z) end
 ---
 ---Sets whether this Shape is a sensor.
 ---
----Sensors do not trigger any collision response, but they still report collisions in `World:collide`.
+---When a Shape is a sensor, it will not generate any collision response when it collides with things, but collisions can still be detected with `World:collide` and `World:getContacts`.
 ---
 ---@param sensor boolean # Whether the Shape should be a sensor.
 function Shape:setSensor(sensor) end
@@ -1225,6 +1281,7 @@ function SliderJoint:getUpperLimit() end
 ---
 ---Sets the axis of the slider.
 ---
+---@overload fun(self: lovr.SliderJoint, axis: lovr.Vec3)
 ---@param x number # The x component of the axis.
 ---@param y number # The y component of the axis.
 ---@param z number # The z component of the axis.
@@ -1267,6 +1324,12 @@ function SphereShape:getRadius() end
 ---@param radius number # The radius of the sphere, in meters.
 function SphereShape:setRadius(radius) end
 
+---
+---A type of `Shape` that can be used for terrains and irregular surfaces.
+---
+---@class lovr.TerrainShape
+local TerrainShape = {}
+
 ---
 ---A World is an object that holds the colliders, joints, and shapes in a physics simulation.
 ---
@@ -1280,11 +1343,15 @@ local World = {}
 ---
 ---Attempt to collide two shapes.
 ---
----Internally this uses joints and forces to ensure the colliders attached to the shapes do not pass through each other.
+---Internally this sets up constraint forces to move the shapes' colliders apart if they are touching.
+---
+---The colliders won't actually move until `World:update` is called again to advance the physics simulation.
 ---
----Collisions can be customized using friction and restitution (bounciness) parameters, and default to using a mix of the colliders' friction and restitution parameters.
+---Collision responses can be customized using friction and restitution (bounciness) parameters, and default to using a mix between the parameters of the two colliders.
 ---
----Usually this is called automatically by `World:update`.
+---Usually this is called internally by `World:update`, or in a custom collision resolver passed to `World:update`.
+---
+---If you want to detect if objects are touching without colliding them, use `World:getContacts` or make one or both of the shapes sensors using `Shape:setSensor`.
 ---
 ---
 ---### NOTE:
@@ -1306,6 +1373,10 @@ function World:collide(shapeA, shapeB, friction, restitution) end
 ---
 ---After calling this function, the `World:overlaps` iterator can be used to iterate over the overlaps, and `World:collide` can be used to resolve a collision for the shapes (if any). Usually this is called automatically by `World:update`.
 ---
+---
+---### NOTE:
+---This performs the "broad phase" culling of objects in the World, usually using a spatial hash or other acceleration structure like a quad tree or octree.
+---
 function World:computeOverlaps() end
 
 ---
@@ -1363,6 +1434,24 @@ function World:getAngularDamping() end
 ---@return table colliders # A table of `Collider` objects.
 function World:getColliders() end
 
+---
+---Computes collision information between two shapes and returns a list of contacts where the shapes intersect.
+---
+---Each contact point consists of a position, a normal vector, and a penetration depth.
+---
+---
+---### NOTE:
+---This only detects collision information, it does not cause the shapes to collide with each other.
+---
+---Use `World:collide` for that.
+---
+---This function ignores collision tags.
+---
+---@param shapeA lovr.Shape # The first shape.
+---@param shapeB lovr.Shape # The second shape.
+---@return table contacts # A list of contacts.  Each contact consists of 7 numbers: the contact position, the normal vector, and a depth value indicating how far the shapes intersect each other at the contact point (`{ x, y, z, nx, ny, nz, depth }`).
+function World:getContacts(shapeA, shapeB) end
+
 ---
 ---Returns the gravity of the World.
 ---
@@ -1414,6 +1503,12 @@ function World:getResponseTime() end
 ---@return number steps # The step count.
 function World:getStepCount() end
 
+---
+---Returns the list of collision tags used when creating the World.
+---
+---@return table tags # A table of collision tags (strings).
+function World:getTags() end
+
 ---
 ---Returns the tightness of joints in the World.
 ---
@@ -1465,6 +1560,7 @@ function World:isSleepingAllowed() end
 ---
 ---Adds a new Collider to the World with a BoxShape already attached.
 ---
+---@overload fun(self: lovr.World, position: lovr.Vec3, size: lovr.Vec3):lovr.Collider
 ---@param x? number # The x coordinate of the center of the box.
 ---@param y? number # The y coordinate of the center of the box.
 ---@param z? number # The z coordinate of the center of the box.
@@ -1477,9 +1573,10 @@ function World:newBoxCollider(x, y, z, width, height, depth) end
 ---
 ---Adds a new Collider to the World with a CapsuleShape already attached.
 ---
----@param x? number # The x coordinate of the center of the capsule.
----@param y? number # The y coordinate of the center of the capsule.
----@param z? number # The z coordinate of the center of the capsule.
+---@overload fun(self: lovr.World, position: lovr.Vec3, radius?: number, length?: number):lovr.Collider
+---@param x? number # The x coordinate of the center of the capsule, in meters.
+---@param y? number # The y coordinate of the center of the capsule, in meters.
+---@param z? number # The z coordinate of the center of the capsule, in meters.
 ---@param radius? number # The radius of the capsule, in meters.
 ---@param length? number # The length of the capsule, not including the caps, in meters.
 ---@return lovr.Collider collider # The new Collider.
@@ -1499,6 +1596,7 @@ function World:newCapsuleCollider(x, y, z, radius, length) end
 ---- `World:newCylinderCollider`
 ---- `World:newSphereCollider`
 ---
+---@overload fun(self: lovr.World, position: lovr.Vec3):lovr.Collider
 ---@param x? number # The x position of the Collider.
 ---@param y? number # The y position of the Collider.
 ---@param z? number # The z position of the Collider.
@@ -1508,9 +1606,10 @@ function World:newCollider(x, y, z) end
 ---
 ---Adds a new Collider to the World with a CylinderShape already attached.
 ---
----@param x? number # The x coordinate of the center of the cylinder.
----@param y? number # The y coordinate of the center of the cylinder.
----@param z? number # The z coordinate of the center of the cylinder.
+---@overload fun(self: lovr.World, position: lovr.Vec3, radius?: number, length?: number):lovr.Collider
+---@param x? number # The x coordinate of the center of the cylinder, in meters.
+---@param y? number # The y coordinate of the center of the cylinder, in meters.
+---@param z? number # The z coordinate of the center of the cylinder, in meters.
 ---@param radius? number # The radius of the cylinder, in meters.
 ---@param length? number # The length of the cylinder, in meters.
 ---@return lovr.Collider collider # The new Collider.
@@ -1528,9 +1627,10 @@ function World:newMeshCollider(vertices, indices) end
 ---
 ---Adds a new Collider to the World with a SphereShape already attached.
 ---
----@param x? number # The x coordinate of the center of the sphere.
----@param y? number # The y coordinate of the center of the sphere.
----@param z? number # The z coordinate of the center of the sphere.
+---@overload fun(self: lovr.World, position: lovr.Vec3, radius?: number):lovr.Collider
+---@param x? number # The x coordinate of the center of the sphere, in meters.
+---@param y? number # The y coordinate of the center of the sphere, in meters.
+---@param z? number # The z coordinate of the center of the sphere, in meters.
 ---@param radius? number # The radius of the sphere, in meters.
 ---@return lovr.Collider collider # The new Collider.
 function World:newSphereCollider(x, y, z, radius) end
@@ -1565,6 +1665,7 @@ function World:overlaps() end
 ---### NOTE:
 ---The callback is passed the shape that was hit, the hit position (in world coordinates), and the normal vector of the hit.
 ---
+---@overload fun(self: lovr.World, start: lovr.Vec3, end: lovr.Vec3, callback: function)
 ---@param x1 number # The x coordinate of the starting position of the ray.
 ---@param y1 number # The y coordinate of the starting position of the ray.
 ---@param z1 number # The z coordinate of the starting position of the ray.
@@ -1590,6 +1691,7 @@ function World:setAngularDamping(damping, threshold) end
 ---
 ---Sets the gravity of the World.
 ---
+---@overload fun(self: lovr.World, gravity: lovr.Vec3)
 ---@param xg number # The x component of the gravity force.
 ---@param yg number # The y component of the gravity force.
 ---@param zg number # The z component of the gravity force.
diff --git a/meta/3rd/lovr/library/lovr/system.lua b/meta/3rd/lovr/library/lovr/system.lua
index de11766b0..c0a5b739c 100644
--- a/meta/3rd/lovr/library/lovr/system.lua
+++ b/meta/3rd/lovr/library/lovr/system.lua
@@ -12,6 +12,25 @@ lovr.system = {}
 ---@return number cores # The number of logical cores on the system.
 function lovr.system.getCoreCount() end
 
+---
+---Returns the position of the mouse.
+---
+---@return number x # The x position of the mouse, relative to the top-left of the window.
+---@return number y # The y position of the mouse, relative to the top-left of the window.
+function lovr.system.getMousePosition() end
+
+---
+---Returns the x position of the mouse.
+---
+---@return number x # The x position of the mouse, relative to the top-left of the window.
+function lovr.system.getMouseX() end
+
+---
+---Returns the y position of the mouse.
+---
+---@return number y # The y position of the mouse, relative to the top-left of the window.
+function lovr.system.getMouseY() end
+
 ---
 ---Returns the current operating system.
 ---
@@ -66,6 +85,13 @@ function lovr.system.getWindowWidth() end
 ---@return boolean down # Whether the key is currently pressed.
 function lovr.system.isKeyDown(key) end
 
+---
+---Returns whether a mouse button is currently pressed.
+---
+---@param button number # The index of a button to check.  Use 1 for the primary mouse button, 2 for the secondary button, and 3 for the middle button.  Other indices can be used, but are hardware-specific.
+---@return boolean down # Whether the mouse button is currently down.
+function lovr.system.isMouseDown(button) end
+
 ---
 ---Returns whether the desktop window is open.
 ---
diff --git a/meta/3rd/lovr/library/lovr/thread.lua b/meta/3rd/lovr/library/lovr/thread.lua
index f281edcb8..5b22c9c32 100644
--- a/meta/3rd/lovr/library/lovr/thread.lua
+++ b/meta/3rd/lovr/library/lovr/thread.lua
@@ -116,9 +116,9 @@ function Channel:pop(wait) end
 ---
 ---Pushes a message onto the Channel.
 ---
----The following types of data can be pushed: nil, boolean, number, string, and userdata.
+---The following types of data can be pushed: nil, boolean, number, string, lightuserdata, vectors, and userdata (LÖVR objects).
 ---
----Tables should be serialized to strings.
+---Notably, tables are not currently supported and should be serialized to strings.
 ---
 ---
 ---### NOTE:
diff --git a/meta/3rd/luv/config.json b/meta/3rd/luv/config.json
new file mode 100644
index 000000000..700803224
--- /dev/null
+++ b/meta/3rd/luv/config.json
@@ -0,0 +1,8 @@
+{
+    "files" : [],
+    "words" : [
+        "require.*'luv'.*",
+        "require.*\"luv\".*"
+    ],
+    "settings" : {}
+}
diff --git a/meta/3rd/luv/library/errors.lua b/meta/3rd/luv/library/errors.lua
new file mode 100644
index 000000000..93184b87f
--- /dev/null
+++ b/meta/3rd/luv/library/errors.lua
@@ -0,0 +1,261 @@
+---@meta
+
+---@alias uv.errno.E2BIG           "E2BIG"           # argument list too long.
+---@alias uv.errno.EACCES          "EACCES"          # permission denied.
+---@alias uv.errno.EADDRINUSE      "EADDRINUSE"      # address already in use.
+---@alias uv.errno.EADDRNOTAVAIL   "EADDRNOTAVAIL"   # address not available.
+---@alias uv.errno.EAFNOSUPPORT    "EAFNOSUPPORT"    # address family not supported.
+---@alias uv.errno.EAGAIN          "EAGAIN"          # resource temporarily unavailable.
+---@alias uv.errno.EAI_ADDRFAMILY  "EAI_ADDRFAMILY"  # address family not supported.
+---@alias uv.errno.EAI_AGAIN       "EAI_AGAIN"       # temporary failure.
+---@alias uv.errno.EAI_BADFLAGS    "EAI_BADFLAGS"    # bad ai_flags value.
+---@alias uv.errno.EAI_BADHINTS    "EAI_BADHINTS"    # invalid value for hints.
+---@alias uv.errno.EAI_CANCELED    "EAI_CANCELED"    # request canceled.
+---@alias uv.errno.EAI_FAIL        "EAI_FAIL"        # permanent failure.
+---@alias uv.errno.EAI_FAMILY      "EAI_FAMILY"      # ai_family not supported.
+---@alias uv.errno.EAI_MEMORY      "EAI_MEMORY"      # out of memory.
+---@alias uv.errno.EAI_NODATA      "EAI_NODATA"      # no address.
+---@alias uv.errno.EAI_NONAME      "EAI_NONAME"      # unknown node or service.
+---@alias uv.errno.EAI_OVERFLOW    "EAI_OVERFLOW"    # argument buffer overflow.
+---@alias uv.errno.EAI_PROTOCOL    "EAI_PROTOCOL"    # resolved protocol is unknown.
+---@alias uv.errno.EAI_SERVICE     "EAI_SERVICE"     # service not available for socket type.
+---@alias uv.errno.EAI_SOCKTYPE    "EAI_SOCKTYPE"    # socket type not supported.
+---@alias uv.errno.EALREADY        "EALREADY"        # connection already in progress.
+---@alias uv.errno.EBADF           "EBADF"           # bad file descriptor.
+---@alias uv.errno.EBUSY           "EBUSY"           # resource busy or locked.
+---@alias uv.errno.ECANCELED       "ECANCELED"       # operation canceled.
+---@alias uv.errno.ECHARSET        "ECHARSET"        # invalid Unicode character.
+---@alias uv.errno.ECONNABORTED    "ECONNABORTED"    # software caused connection abort.
+---@alias uv.errno.ECONNREFUSED    "ECONNREFUSED"    # connection refused.
+---@alias uv.errno.ECONNRESET      "ECONNRESET"      # connection reset by peer.
+---@alias uv.errno.EDESTADDRREQ    "EDESTADDRREQ"    # destination address required.
+---@alias uv.errno.EEXIST          "EEXIST"          # file already exists.
+---@alias uv.errno.EFAULT          "EFAULT"          # bad address in system call argument.
+---@alias uv.errno.EFBIG           "EFBIG"           # file too large.
+---@alias uv.errno.EFTYPE          "EFTYPE"          # inappropriate file type or format.
+---@alias uv.errno.EHOSTUNREACH    "EHOSTUNREACH"    # host is unreachable.
+---@alias uv.errno.EILSEQ          "EILSEQ"          # illegal byte sequence.
+---@alias uv.errno.EINTR           "EINTR"           # interrupted system call.
+---@alias uv.errno.EINVAL          "EINVAL"          # invalid argument.
+---@alias uv.errno.EIO             "EIO"             # i/o error.
+---@alias uv.errno.EISCONN         "EISCONN"         # socket is already connected.
+---@alias uv.errno.EISDIR          "EISDIR"          # illegal operation on a directory.
+---@alias uv.errno.ELOOP           "ELOOP"           # too many symbolic links encountered.
+---@alias uv.errno.EMFILE          "EMFILE"          # too many open files.
+---@alias uv.errno.EMLINK          "EMLINK"          # too many links.
+---@alias uv.errno.EMSGSIZE        "EMSGSIZE"        # message too long.
+---@alias uv.errno.ENAMETOOLONG    "ENAMETOOLONG"    # name too long.
+---@alias uv.errno.ENETDOWN        "ENETDOWN"        # network is down.
+---@alias uv.errno.ENETUNREACH     "ENETUNREACH"     # network is unreachable.
+---@alias uv.errno.ENFILE          "ENFILE"          # file table overflow.
+---@alias uv.errno.ENOBUFS         "ENOBUFS"         # no buffer space available.
+---@alias uv.errno.ENODEV          "ENODEV"          # no such device.
+---@alias uv.errno.ENOENT          "ENOENT"          # no such file or directory.
+---@alias uv.errno.ENOMEM          "ENOMEM"          # not enough memory.
+---@alias uv.errno.ENONET          "ENONET"          # machine is not on the network.
+---@alias uv.errno.ENOPROTOOPT     "ENOPROTOOPT"     # protocol not available.
+---@alias uv.errno.ENOSPC          "ENOSPC"          # no space left on device.
+---@alias uv.errno.ENOSYS          "ENOSYS"          # function not implemented.
+---@alias uv.errno.ENOTCONN        "ENOTCONN"        # socket is not connected.
+---@alias uv.errno.ENOTDIR         "ENOTDIR"         # not a directory.
+---@alias uv.errno.ENOTEMPTY       "ENOTEMPTY"       # directory not empty.
+---@alias uv.errno.ENOTSOCK        "ENOTSOCK"        # socket operation on non-socket.
+---@alias uv.errno.ENOTSUP         "ENOTSUP"         # operation not supported on socket.
+---@alias uv.errno.ENOTTY          "ENOTTY"          # inappropriate ioctl for device.
+---@alias uv.errno.ENXIO           "ENXIO"           # no such device or address.
+---@alias uv.errno.EOF             "EOF"             # end of file.
+---@alias uv.errno.EOVERFLOW       "EOVERFLOW"       # value too large for defined data type.
+---@alias uv.errno.EPERM           "EPERM"           # operation not permitted.
+---@alias uv.errno.EPIPE           "EPIPE"           # broken pipe.
+---@alias uv.errno.EPROTO          "EPROTO"          # protocol error.
+---@alias uv.errno.EPROTONOSUPPORT "EPROTONOSUPPORT" # protocol not supported.
+---@alias uv.errno.EPROTOTYPE      "EPROTOTYPE"      # protocol wrong type for socket.
+---@alias uv.errno.ERANGE          "ERANGE"          # result too large.
+---@alias uv.errno.EROFS           "EROFS"           # read-only file system.
+---@alias uv.errno.ESHUTDOWN       "ESHUTDOWN"       # cannot send after transport endpoint shutdown.
+---@alias uv.errno.ESOCKTNOSUPPORT "ESOCKTNOSUPPORT" # socket type not supported.
+---@alias uv.errno.ESPIPE          "ESPIPE"          # invalid seek.
+---@alias uv.errno.ESRCH           "ESRCH"           # no such process.
+---@alias uv.errno.ETIMEDOUT       "ETIMEDOUT"       # connection timed out.
+---@alias uv.errno.ETXTBSY         "ETXTBSY"         # text file is busy.
+---@alias uv.errno.EXDEV           "EXDEV"           # cross-device link not permitted.
+---@alias uv.errno.UNKNOWN         "UNKNOWN"         # unknown error.
+
+
+--- An error string with the format {name}: {message}
+---
+--- * {name} is the error name provided internally by uv_err_name (e.g. "ENOENT")
+--- * {message} is a human-readable message provided internally by uv_strerror
+---
+---@alias uv.error.message string
+
+
+--- An error name string
+---
+---@alias uv.error.name
+---| "E2BIG"           # argument list too long
+---| "EACCES"          # permission denied
+---| "EADDRINUSE"      # address already in use
+---| "EADDRNOTAVAIL"   # address not available
+---| "EAFNOSUPPORT"    # address family not supported
+---| "EAGAIN"          # resource temporarily unavailable
+---| "EAI_ADDRFAMILY"  # address family not supported
+---| "EAI_AGAIN"       # temporary failure
+---| "EAI_BADFLAGS"    # bad ai_flags value
+---| "EAI_BADHINTS"    # invalid value for hints
+---| "EAI_CANCELED"    # request canceled
+---| "EAI_FAIL"        # permanent failure
+---| "EAI_FAMILY"      # ai_family not supported
+---| "EAI_MEMORY"      # out of memory
+---| "EAI_NODATA"      # no address
+---| "EAI_NONAME"      # unknown node or service
+---| "EAI_OVERFLOW"    # argument buffer overflow
+---| "EAI_PROTOCOL"    # resolved protocol is unknown
+---| "EAI_SERVICE"     # service not available for socket type
+---| "EAI_SOCKTYPE"    # socket type not supported
+---| "EALREADY"        # connection already in progress
+---| "EBADF"           # bad file descriptor
+---| "EBUSY"           # resource busy or locked
+---| "ECANCELED"       # operation canceled
+---| "ECHARSET"        # invalid Unicode character
+---| "ECONNABORTED"    # software caused connection abort
+---| "ECONNREFUSED"    # connection refused
+---| "ECONNRESET"      # connection reset by peer
+---| "EDESTADDRREQ"    # destination address required
+---| "EEXIST"          # file already exists
+---| "EFAULT"          # bad address in system call argument
+---| "EFBIG"           # file too large
+---| "EFTYPE"          # inappropriate file type or format
+---| "EHOSTUNREACH"    # host is unreachable
+---| "EILSEQ"          # illegal byte sequence
+---| "EINTR"           # interrupted system call
+---| "EINVAL"          # invalid argument
+---| "EIO"             # i/o error
+---| "EISCONN"         # socket is already connected
+---| "EISDIR"          # illegal operation on a directory
+---| "ELOOP"           # too many symbolic links encountered
+---| "EMFILE"          # too many open files
+---| "EMLINK"          # too many links
+---| "EMSGSIZE"        # message too long
+---| "ENAMETOOLONG"    # name too long
+---| "ENETDOWN"        # network is down
+---| "ENETUNREACH"     # network is unreachable
+---| "ENFILE"          # file table overflow
+---| "ENOBUFS"         # no buffer space available
+---| "ENODEV"          # no such device
+---| "ENOENT"          # no such file or directory
+---| "ENOMEM"          # not enough memory
+---| "ENONET"          # machine is not on the network
+---| "ENOPROTOOPT"     # protocol not available
+---| "ENOSPC"          # no space left on device
+---| "ENOSYS"          # function not implemented
+---| "ENOTCONN"        # socket is not connected
+---| "ENOTDIR"         # not a directory
+---| "ENOTEMPTY"       # directory not empty
+---| "ENOTSOCK"        # socket operation on non-socket
+---| "ENOTSUP"         # operation not supported on socket
+---| "ENOTTY"          # inappropriate ioctl for device
+---| "ENXIO"           # no such device or address
+---| "EOF"             # end of file
+---| "EOVERFLOW"       # value too large for defined data type
+---| "EPERM"           # operation not permitted
+---| "EPIPE"           # broken pipe
+---| "EPROTO"          # protocol error
+---| "EPROTONOSUPPORT" # protocol not supported
+---| "EPROTOTYPE"      # protocol wrong type for socket
+---| "ERANGE"          # result too large
+---| "EROFS"           # read-only file system
+---| "ESHUTDOWN"       # cannot send after transport endpoint shutdown
+---| "ESOCKTNOSUPPORT" # socket type not supported
+---| "ESPIPE"          # invalid seek
+---| "ESRCH"           # no such process
+---| "ETIMEDOUT"       # connection timed out
+---| "ETXTBSY"         # text file is busy
+---| "EXDEV"           # cross-device link not permitted
+---| "UNKNOWN"         # unknown error
+
+
+--- A table value which exposes error constants as a map, where the key is the error name (without the UV_ prefix) and its value is a negative number.
+---
+---@class uv.errno : table
+---
+---@field E2BIG           integer # argument list too long.
+---@field EACCES          integer # permission denied.
+---@field EADDRINUSE      integer # address already in use.
+---@field EADDRNOTAVAIL   integer # address not available.
+---@field EAFNOSUPPORT    integer # address family not supported.
+---@field EAGAIN          integer # resource temporarily unavailable.
+---@field EAI_ADDRFAMILY  integer # address family not supported.
+---@field EAI_AGAIN       integer # temporary failure.
+---@field EAI_BADFLAGS    integer # bad ai_flags value.
+---@field EAI_BADHINTS    integer # invalid value for hints.
+---@field EAI_CANCELED    integer # request canceled.
+---@field EAI_FAIL        integer # permanent failure.
+---@field EAI_FAMILY      integer # ai_family not supported.
+---@field EAI_MEMORY      integer # out of memory.
+---@field EAI_NODATA      integer # no address.
+---@field EAI_NONAME      integer # unknown node or service.
+---@field EAI_OVERFLOW    integer # argument buffer overflow.
+---@field EAI_PROTOCOL    integer # resolved protocol is unknown.
+---@field EAI_SERVICE     integer # service not available for socket type.
+---@field EAI_SOCKTYPE    integer # socket type not supported.
+---@field EALREADY        integer # connection already in progress.
+---@field EBADF           integer # bad file descriptor.
+---@field EBUSY           integer # resource busy or locked.
+---@field ECANCELED       integer # operation canceled.
+---@field ECHARSET        integer # invalid Unicode character.
+---@field ECONNABORTED    integer # software caused connection abort.
+---@field ECONNREFUSED    integer # connection refused.
+---@field ECONNRESET      integer # connection reset by peer.
+---@field EDESTADDRREQ    integer # destination address required.
+---@field EEXIST          integer # file already exists.
+---@field EFAULT          integer # bad address in system call argument.
+---@field EFBIG           integer # file too large.
+---@field EFTYPE          integer # inappropriate file type or format.
+---@field EHOSTUNREACH    integer # host is unreachable.
+---@field EILSEQ          integer # illegal byte sequence.
+---@field EINTR           integer # interrupted system call.
+---@field EINVAL          integer # invalid argument.
+---@field EIO             integer # i/o error.
+---@field EISCONN         integer # socket is already connected.
+---@field EISDIR          integer # illegal operation on a directory.
+---@field ELOOP           integer # too many symbolic links encountered.
+---@field EMFILE          integer # too many open files.
+---@field EMLINK          integer # too many links.
+---@field EMSGSIZE        integer # message too long.
+---@field ENAMETOOLONG    integer # name too long.
+---@field ENETDOWN        integer # network is down.
+---@field ENETUNREACH     integer # network is unreachable.
+---@field ENFILE          integer # file table overflow.
+---@field ENOBUFS         integer # no buffer space available.
+---@field ENODEV          integer # no such device.
+---@field ENOENT          integer # no such file or directory.
+---@field ENOMEM          integer # not enough memory.
+---@field ENONET          integer # machine is not on the network.
+---@field ENOPROTOOPT     integer # protocol not available.
+---@field ENOSPC          integer # no space left on device.
+---@field ENOSYS          integer # function not implemented.
+---@field ENOTCONN        integer # socket is not connected.
+---@field ENOTDIR         integer # not a directory.
+---@field ENOTEMPTY       integer # directory not empty.
+---@field ENOTSOCK        integer # socket operation on non-socket.
+---@field ENOTSUP         integer # operation not supported on socket.
+---@field ENOTTY          integer # inappropriate ioctl for device.
+---@field ENXIO           integer # no such device or address.
+---@field EOF             integer # end of file.
+---@field EOVERFLOW       integer # value too large for defined data type.
+---@field EPERM           integer # operation not permitted.
+---@field EPIPE           integer # broken pipe.
+---@field EPROTO          integer # protocol error.
+---@field EPROTONOSUPPORT integer # protocol not supported.
+---@field EPROTOTYPE      integer # protocol wrong type for socket.
+---@field ERANGE          integer # result too large.
+---@field EROFS           integer # read-only file system.
+---@field ESHUTDOWN       integer # cannot send after transport endpoint shutdown.
+---@field ESOCKTNOSUPPORT integer # socket type not supported.
+---@field ESPIPE          integer # invalid seek.
+---@field ESRCH           integer # no such process.
+---@field ETIMEDOUT       integer # connection timed out.
+---@field ETXTBSY         integer # text file is busy.
+---@field EXDEV           integer # cross-device link not permitted.
+---@field UNKNOWN         integer # unknown error.
diff --git a/meta/3rd/luv/library/luv_dir_t.lua b/meta/3rd/luv/library/luv_dir_t.lua
new file mode 100644
index 000000000..344178026
--- /dev/null
+++ b/meta/3rd/luv/library/luv_dir_t.lua
@@ -0,0 +1,34 @@
+---@meta
+
+--- luv_dir_t
+---
+---@class uv.luv_dir_t : userdata
+local dir
+
+--- Closes a directory stream returned by a successful `uv.fs_opendir()` call.
+---
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(self:uv.luv_dir_t, callback:uv.fs_closedir.callback):uv.uv_fs_t
+function dir:closedir() end
+
+--- Iterates over the directory stream `luv_dir_t` returned by a successful
+--- `uv.fs_opendir()` call. A table of data tables is returned where the number
+--- of entries `n` is equal to or less than the `entries` parameter used in
+--- the associated `uv.fs_opendir()` call.
+---
+--- **Returns (sync version):** `table` or `fail`
+--- - `[1, 2, 3, ..., n]` : `table`
+---   - `name` : `string`
+---   - `type` : `string`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@return uv.fs_readdir.entry[]|nil entries
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(self:uv.luv_dir_t, callback:uv.fs_readdir.callback):uv.uv_fs_t
+function dir:readdir() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/luv_thread_t.lua b/meta/3rd/luv/library/luv_thread_t.lua
new file mode 100644
index 000000000..3d199657f
--- /dev/null
+++ b/meta/3rd/luv/library/luv_thread_t.lua
@@ -0,0 +1,20 @@
+---@meta
+
+--- luv_thread_t
+---
+---@class uv.luv_thread_t : userdata
+local thread
+
+--- Returns a boolean indicating whether two threads are the same. This function is
+--- equivalent to the `__eq` metamethod.
+---
+---@param other_thread uv.luv_thread_t
+---@return boolean
+function thread:equal(other_thread) end
+
+--- Waits for the `thread` to finish executing its entry function.
+---
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function thread:join() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/luv_work_ctx_t.lua b/meta/3rd/luv/library/luv_work_ctx_t.lua
new file mode 100644
index 000000000..790db9956
--- /dev/null
+++ b/meta/3rd/luv/library/luv_work_ctx_t.lua
@@ -0,0 +1,17 @@
+---@meta
+
+--- luv_work_ctx_t
+---
+---@class uv.luv_work_ctx_t : userdata
+local work_ctx
+
+--- Queues a work request which will run `work_callback` in a new Lua state in a
+--- thread from the threadpool with any additional arguments from `...`. Values
+--- returned from `work_callback` are passed to `after_work_callback`, which is
+--- called in the main loop thread.
+---
+---@param  ...         uv.threadargs
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function work_ctx:queue(...) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/types.lua b/meta/3rd/luv/library/types.lua
new file mode 100644
index 000000000..57bce70c0
--- /dev/null
+++ b/meta/3rd/luv/library/types.lua
@@ -0,0 +1,526 @@
+---@meta
+
+---@class uv.getrusage.result.time_t
+---@field sec integer
+---@field usec integer
+
+---@class uv.getrusage.result
+---
+---@field utime    uv.getrusage.result.time_t # user CPU time used
+---@field stime    uv.getrusage.result.time_t # system CPU time used
+---@field maxrss   integer                    # maximum resident set size
+---@field ixrss    integer                    # integral shared memory size
+---@field idrss    integer                    # integral unshared data size
+---@field isrss    integer                    # integral unshared stack size
+---@field minflt   integer                    # page reclaims (soft page faults)
+---@field majflt   integer                    # page faults (hard page faults)
+---@field nswap    integer                    # swaps
+---@field inblock  integer                    # block input operations
+---@field oublock  integer                    # block output operations
+---@field msgsnd   integer                    # IPC messages sent
+---@field msgrcv   integer                    # IPC messages received
+---@field nsignals integer                    # signals received
+---@field nvcsw    integer                    # voluntary context switches
+---@field nivcsw   integer                    # involuntary context switches
+
+
+---@alias uv.spawn.options.stdio.fd integer
+
+---@alias uv.spawn.options.stdio.stream uv.uv_stream_t
+
+
+--- The `options.stdio` entries can take many shapes.
+---
+---   - If they are numbers, then the child process inherits that same zero-indexed fd from the parent process.
+---   - If `uv_stream_t` handles are passed in, those are used as a read-write pipe or inherited stream depending if the stream has a valid fd.
+---   - Including `nil` placeholders means to ignore that fd in the child process.
+---
+---@alias uv.spawn.options.stdio
+---| integer
+---| uv.uv_stream_t
+---| nil
+
+
+---@class uv.spawn.options : table
+---
+--- Command line arguments as a list of strings. The first string should be the path to the program. On Windows, this uses CreateProcess which concatenates the arguments into a string. This can cause some strange errors. (See `options.verbatim` below for Windows.)
+---@field args string[]
+---
+--- Set environment variables for the new process.
+---@field env table<string, string>
+---
+--- Set the current working directory for the sub-process.
+---@field cwd string
+---
+--- Set the child process' user id.
+---@field uid string
+---
+--- Set the child process' group id.
+---@field gid string
+---
+--- If true, do not wrap any arguments in quotes, or perform any other escaping, when converting the argument list into a command line string. This option is only meaningful on Windows systems. On Unix it is silently ignored.
+---@field verbatim boolean
+---
+--- If true, spawn the child process in a detached state - this will make it a process group leader, and will effectively enable the child to keep running after the parent exits. Note that the child process will still keep the parent's event loop alive unless the parent process calls `uv.unref()` on the child's process handle.
+---@field detached boolean
+---
+--- If true, hide the subprocess console window that would normally be created. This option is only meaningful on Windows systems. On Unix it is silently ignored.
+---@field hide boolean
+---
+--- Set the file descriptors that will be made available to the child process. The convention is that the first entries are stdin, stdout, and stderr. (**Note**: On Windows, file descriptors after the third are available to the child process only if the child processes uses the MSVCRT runtime.)
+---@field stdio { [1]: uv.spawn.options.stdio, [2]: uv.spawn.options.stdio, [3]: uv.spawn.options.stdio }
+
+
+---@class uv.fs_stat.result.time
+---@field sec integer
+---@field nsec integer
+
+---@class uv.fs_stat.result
+---
+---@field dev       integer
+---@field mode      integer
+---@field nlink     integer
+---@field uid       integer
+---@field gid       integer
+---@field rdev      integer
+---@field ino       integer
+---@field size      integer
+---@field blksize   integer
+---@field blocks    integer
+---@field flags     integer
+---@field gen       integer
+---@field atime     uv.fs_stat.result.time
+---@field mtime     uv.fs_stat.result.time
+---@field ctime     uv.fs_stat.result.time
+---@field birthtime uv.fs_stat.result.time
+---@field type      string
+
+
+---@class uv.fs_statfs.result
+---
+---@field type   integer
+---@field bsize  integer
+---@field blocks integer
+---@field bfree  integer
+---@field bavail integer
+---@field files  integer
+---@field ffree  integer
+
+
+
+---@class uv.getaddrinfo.hints : table
+---@field family      string|integer|uv.socket.family
+---@field socktype    string|integer|uv.socket.type
+---@field protocol    string|integer|uv.socket.protocol
+---@field addrconfig  boolean
+---@field v4mapped    boolean
+---@field all         boolean
+---@field numerichost boolean
+---@field passive     boolean
+---@field numericserv boolean
+---@field canonname   boolean
+
+
+--- uv.getnameinfo.address
+---
+---@class uv.getnameinfo.address : table
+---@field ip     string
+---@field port   integer
+---@field family string|integer
+
+
+--- uv.new_thread.options
+---
+---@class uv.new_thread.options : table
+---@field stack_size integer
+
+
+--- uv.pipe.read_flags
+---
+---@class uv.pipe.read_flags : table
+---@field nonblock boolean|false
+
+
+--- uv.pipe.write_flags
+---
+---@class uv.pipe.write_flags : table
+---@field nonblock boolean|false
+
+---@alias uv.socketpair.fds { [1]: integer, [2]: integer }
+
+--- uv.socketpair.flags
+---
+---@class uv.socketpair.flags : table
+---
+--- Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise.
+---@field nonblock true|false
+
+---@alias uv.socket.protocol
+---| string
+---| "ip"
+---| "icmp"
+---| "tcp"
+---| "udp"
+
+---@alias uv.socket.type
+---| "stream"
+---| "dgram"
+---| "raw"
+---| "rdm"
+---| "seqpacket"
+
+--- When `protocol` is set to 0 or nil, it will be automatically chosen based on the socket's domain and type.
+---
+--- When `protocol` is specified as a string, it will be looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc).
+---
+---@alias uv.socketpair.protocol
+---| 0   # automatically choose based on socket domain/type
+---| nil # automatically choose based on socket domain/type
+---| uv.socket.protocol
+
+---@alias uv.socketpair.socktype
+---| uv.socket.type
+---| integer
+---| nil
+
+--- uv.tcp_bind.flags
+---
+---@class uv.tcp_bind.flags : table
+---@field ipv6only boolean
+
+
+--- uv.udp_bind.flags
+---
+---
+---@class uv.udp_bind.flags : table
+---@field ipv6only boolean
+---@field reuseaddr boolean
+
+--- TTY mode is a C enum with the following values:
+---
+---@alias uv.tty.mode
+---| 0 # UV_TTY_MODE_NORMAL: Initial/normal terminal mode
+---| 1 # UV_TTY_MODE_RAW: Raw input mode (On Windows, ENABLE_WINDOW_INPUT is also enabled)
+---| 2 # UV_TTY_MODE_IO: Binary-safe I/O mode for IPC (Unix-only)
+
+
+--- uv.run() modes:
+---
+---   - `"default"`: Runs the event loop until there are no more active and
+---   referenced handles or requests. Returns `true` if `uv.stop()` was called and
+---   there are still active handles or requests. Returns `false` in all other
+---   cases.
+---
+---   - `"once"`: Poll for I/O once. Note that this function blocks if there are no
+---   pending callbacks. Returns `false` when done (no active handles or requests
+---   left), or `true` if more callbacks are expected (meaning you should run the
+---   event loop again sometime in the future).
+---
+---   - `"nowait"`: Poll for I/O once but don't block if there are no pending
+---   callbacks. Returns `false` if done (no active handles or requests left),
+---   or `true` if more callbacks are expected (meaning you should run the event
+---   loop again sometime in the future).
+---
+---@alias uv.run.mode
+---| "default" # Runs the event loop until there are no more active and referenced handles or requests.
+---| "once" # Poll for I/O once.
+---| "nowait" # Poll for I/O once but don't block if there are no pending callbacks.
+
+
+---@class uv.interface_addresses.addr
+---
+---@field ip       string
+---@field family   string
+---@field netmask  string
+---@field internal boolean
+---@field mac      string
+
+---@class uv.new_udp.flags
+---
+--- When specified, `mmsgs` determines the number of messages able to be received at one time via `recvmmsg(2)` (the allocated buffer will be sized to be able to fit the specified number of max size dgrams). Only has an effect on platforms that support `recvmmsg(2)`.
+---@field mmsgs integer|nil # default `1`
+---
+---@field family uv.new_udp.flags.family|nil
+
+---@alias uv.new_udp.flags.family uv.socket.family
+
+---@alias uv.socket.family
+---| "unix"
+---| "inet"
+---| "inet6"
+---| "ipx"
+---| "netlink"
+---| "x25"
+---| "ax25"
+---| "atmpvc"
+---| "appletalk"
+---| "packet"
+
+
+---@class uv.os_get_passwd.info
+---@field username string
+---@field uid integer
+---@field gid integer
+---@field shell string
+---@field homedir string
+
+---@class uv.os_uname.info
+---@field sysname string
+---@field release string
+---@field version string
+---@field machine string
+
+---@alias uv.pipe_chmod.flags
+---| "r"
+---| "rw"
+---| "w"
+---| "wr"
+
+--- `r` is `READABLE`
+--- `w` is `WRITABLE`
+--- `d` is `DISCONNECT`
+--- `p` is `PRIORITIZED`
+---
+---@alias uv.poll.eventspec
+---| "r"
+---| "rd"
+---| "rw"
+---| "rp"
+---| "rdp"
+---| "rwd"
+---| "rwp"
+---| "rwdp"
+---| "d"
+---| "dp"
+---| "w"
+---| "wd"
+---| "wp"
+---| "wdp"
+---| "p"
+
+
+
+--- socket info
+---
+---@class uv.socketinfo : table
+---@field ip string
+---@field family string|uv.socket.family
+---@field port integer
+
+
+--- uv.udp.sockname
+---
+---@alias uv.udp.sockname uv.socketinfo
+
+
+---@class uv.uv_fs_t          : uv.uv_req_t
+---@class uv.uv_write_t       : uv.uv_req_t
+---@class uv.uv_connect_t     : uv.uv_req_t
+---@class uv.uv_shutdown_t    : uv.uv_req_t
+---@class uv.uv_udp_send_t    : uv.uv_req_t
+---@class uv.uv_getaddrinfo_t : uv.uv_req_t
+---@class uv.uv_getnameinfo_t : uv.uv_req_t
+---@class uv.uv_work_t        : uv.uv_req_t
+
+---@class uv.pipe.fds
+---@field read integer
+---@field write integer
+
+---@class uv.fs_copyfile.flags_t : table
+---@field excl          boolean
+---@field ficlone       boolean
+---@field ficlone_force boolean
+
+---@alias uv.fs_copyfile.flags
+---| uv.fs_copyfile.flags_t
+---| integer
+
+
+---@class uv.fs_event_start.flags : table
+---@field watch_entry boolean|nil # default: false
+---@field stat        boolean|nil # default: false
+---@field recursive   boolean|nil # default: false
+
+---@class uv.fs_event_start.callback.events : table
+---@field change boolean|nil
+---@field rename boolean|nil
+
+--- Event loop
+---
+--- The event loop is the central part of libuv's functionality. It takes care of
+--- polling for I/O and scheduling callbacks to be run based on different sources of
+--- events.
+---
+--- In luv, there is an implicit uv loop for every Lua state that loads the library.
+--- You can use this library in an multi-threaded environment as long as each thread
+--- has it's own Lua state with its corresponding own uv loop. This loop is not
+--- directly exposed to users in the Lua module.
+---
+---@class uv.uv_loop_t : table
+
+---@alias uv.threadargs any
+
+--- Luv APIS that accept a "buffer" type will accept either a string or an array-like table of strings
+---@alias uv.buffer string|string[]
+
+---@alias uv.fs_mkdtemp.callback           fun(err: uv.callback.err, path?:string)
+---@alias uv.fs_access.callback            fun(err: uv.callback.err, permission?:boolean)
+---@alias uv.fs_event_start.callback       fun(err: uv.callback.err, filename:string, events:uv.fs_event_start.callback.events)
+---@alias uv.fs_poll_start.callback        fun(err: uv.callback.err, prev:uv.fs_stat.result|nil, curr:uv.fs_stat.result|nil)
+---@alias uv.fs_fstat.callback             fun(err: uv.callback.err, stat:uv.fs_stat.result|nil)
+---@alias uv.fs_lstat.callback             fun(err: uv.callback.err, stat:uv.fs_stat.result|nil)
+---@alias uv.fs_mkstemp.callback           fun(err: uv.callback.err, fd?:integer, path?:string)
+---@alias uv.fs_opendir.callback           fun(err: uv.callback.err, dir?:uv.luv_dir_t)
+---@alias uv.fs_open.callback              fun(err: uv.callback.err, fd?:integer)
+---@alias uv.fs_read.callback              fun(err: uv.callback.err, data?:string)
+---@alias uv.fs_scandir.callback           fun(err: uv.callback.err, success?:uv.uv_fs_t)
+---@alias uv.fs_sendfile.callback          fun(err: uv.callback.err, bytes?:integer)
+---@alias uv.fs_stat.callback              fun(err: uv.callback.err, stat: uv.fs_stat.result|nil)
+---@alias uv.getaddrinfo.callback          fun(err: uv.callback.err, addresses:uv.getaddrinfo.result[]|nil)
+---@alias uv.getnameinfo.callback          fun(err: uv.callback.err, host?:string, service?:string)
+---@alias uv.fs_write.callback             fun(err: uv.callback.err, bytes?:integer)
+---@alias uv.new_async.callback            fun(...:uv.threadargs)
+---@alias uv.new_work.after_work_callback  fun(...:uv.threadargs)
+---@alias uv.new_work.work_callback        fun(...:uv.threadargs)
+---@alias uv.poll_start.callback           fun(err: uv.callback.err, events?:string)
+---@alias uv.random.callback               fun(err: uv.callback.err, bytes?:string)
+---@alias uv.read_start.callback           fun(err: uv.callback.err, data?:string)
+---@alias uv.signal_start.callback         fun(signum:string)
+---@alias uv.signal_start_oneshot.callback fun(signum:string)
+---@alias uv.spawn.on_exit                 fun(code:integer, signal:integer)
+---@alias uv.fs_readlink.callback          fun(err: uv.callback.err, path?:string)
+---@alias uv.fs_realpath.callback          fun(err: uv.callback.err, path?:string)
+---@alias uv.fs_readdir.callback           fun(err: uv.callback.err, entries: uv.fs_readdir.entry[]|nil)
+
+
+---@class uv.fs_readdir.entry : table
+---@field name string
+---@field type string
+
+
+---@class uv.udp_recv_start.callback.addr : table
+---@field ip string
+---@field port integer
+---@field family uv.socket.family|string
+
+---@class uv.udp_recv_start.callback.flags : table
+---@field partial boolean|nil
+---@field mmsg_chunk boolean|nil
+
+---@alias uv.udp_recv_start.callback fun(err:string|nil, data:string|nil, addr:uv.udp_recv_start.callback.addr|nil, flags:uv.udp_recv_start.callback.flags)
+
+---@alias uv.walk.callback fun(handle:uv.uv_handle_t)
+
+---@alias uv.fs_statfs.callback    fun(err: uv.callback.err, stat: uv.fs_statfs.result|nil)
+
+---@alias uv.pipe_connect.callback uv.callback
+---@alias uv.shutdown.callback     uv.callback
+---@alias uv.tcp_connect.callback  uv.callback
+---@alias uv.udp_send.callback     uv.callback
+---@alias uv.write.callback        uv.callback
+---@alias uv.write2.callback       uv.callback
+---@alias uv.listen.callback       uv.callback
+
+---@alias uv.fs_closedir.callback  uv.callback_with_success
+---@alias uv.fs_copyfile.callback  uv.callback_with_success
+---@alias uv.fs_symlink.callback   uv.callback_with_success
+---@alias uv.fs_unlink.callback    uv.callback_with_success
+---@alias uv.fs_utime.callback     uv.callback_with_success
+---@alias uv.fs_chmod.callback     uv.callback_with_success
+---@alias uv.fs_chown.callback     uv.callback_with_success
+---@alias uv.fs_close.callback     uv.callback_with_success
+---@alias uv.fs_fchmod.callback    uv.callback_with_success
+---@alias uv.fs_fchown.callback    uv.callback_with_success
+---@alias uv.fs_fdatasync.callback uv.callback_with_success
+---@alias uv.fs_fsync.callback     uv.callback_with_success
+---@alias uv.fs_ftruncate.callback uv.callback_with_success
+---@alias uv.fs_futime.callback    uv.callback_with_success
+---@alias uv.fs_lchown.callback    uv.callback_with_success
+---@alias uv.fs_link.callback      uv.callback_with_success
+---@alias uv.fs_lutime.callback    uv.callback_with_success
+---@alias uv.fs_mkdir.callback     uv.callback_with_success
+---@alias uv.fs_rename.callback    uv.callback_with_success
+---@alias uv.fs_rmdir.callback     uv.callback_with_success
+
+---@class uv.fs_symlink.flags : table
+---@field dir boolean
+---@field junction boolean
+
+---@class uv.getaddrinfo.result : table
+---
+---@field addr      string
+---@field family    uv.socket.family|string
+---@field port      integer|nil
+---@field socktype  string
+---@field protocol  string
+---@field canonname string|nil
+
+---@alias uv.callback.err string|nil
+
+---@alias uv.callback.success boolean|nil
+
+---@alias uv.callback fun(err: uv.callback.err)
+
+---@alias uv.callback_with_success fun(err: uv.callback.err, success: uv.callback.success)
+
+
+---@alias uv.fs_open.flags string
+---| "r"
+---| "rs"
+---| "sr"
+---| "r+"
+---| "rs+"
+---| "sr+"
+---| "w"
+---| "wx"
+---| "xw"
+---| "w+"
+---| "wx+"
+---| "xw+"
+---| "a"
+---| "ax"
+---| "xa"
+---| "a+"
+---| "ax+"
+---| "xa+"
+---| integer
+
+
+---@class uv.cpu_info.cpu : table
+---@field model string
+---@field speed number
+---@field times uv.cpu_info.cpu.times
+
+---@class uv.cpu_info.cpu.times : table
+---@field user number
+---@field nice number
+---@field sys  number
+---@field idle number
+---@field irq  number
+
+
+--- The name of the struct for a given request (e.g. `"fs"` for `uv_fs_t`)
+--- and the libuv enum integer for the request's type (`uv_req_type`).
+---
+---@alias uv.req_type.name
+---| "async"
+---| "check"
+---| "fs_event"
+---| "fs_poll"
+---| "handle"
+---| "idle"
+---| "pipe"
+---| "poll"
+---| "prepare"
+---| "process"
+---| "req"
+---| "signal"
+---| "stream"
+---| "tcp"
+---| "timer"
+---| "tty"
+---| "udp"
+
+
+--- The libuv enum integer for the request's type (`uv_req_type`).
+---@alias uv.req_type.enum integer
diff --git a/meta/3rd/luv/library/uv.lua b/meta/3rd/luv/library/uv.lua
new file mode 100644
index 000000000..bce0c22fb
--- /dev/null
+++ b/meta/3rd/luv/library/uv.lua
@@ -0,0 +1,3075 @@
+---@meta
+
+--- The luv project provides access to the multi-platform support library
+--- libuv in Lua code. It was primarily developed for the luvit project as
+--- the built-in `uv` module, but can be used in other Lua environments.
+---
+--- More information about the core libuv library can be found at the original
+--- libuv documentation page.
+---
+---
+--- Here is a small example showing a TCP echo server:
+---
+--- ```lua
+--- local uv = require("luv") -- "luv" when stand-alone, "uv" in luvi apps
+---
+--- local server = uv.new_tcp()
+--- server:bind("127.0.0.1", 1337)
+--- server:listen(128, function (err)
+---   assert(not err, err)
+---   local client = uv.new_tcp()
+---   server:accept(client)
+---   client:read_start(function (err, chunk)
+---     assert(not err, err)
+---     if chunk then
+---       client:write(chunk)
+---     else
+---       client:shutdown()
+---       client:close()
+---     end
+---   end)
+--- end)
+--- print("TCP server listening at 127.0.0.1 port 1337")
+--- uv.run() -- an explicit run call is necessary outside of luvit
+--- ```
+---
+---
+--- The luv library contains a single Lua module referred to hereafter as `uv` for
+--- simplicity. This module consists mostly of functions with names corresponding to
+--- their original libuv versions. For example, the libuv function `uv_tcp_bind` has
+--- a luv version at `uv.tcp_bind`. Currently, only one non-function field exists:
+--- `uv.constants`, which is a table.
+---
+---
+--- In addition to having simple functions, luv provides an optional method-style
+--- API. For example, `uv.tcp_bind(server, host, port)` can alternatively be called
+--- as `server:bind(host, port)`. Note that the first argument `server` becomes the
+--- object and `tcp_` is removed from the function name. Method forms are
+--- documented below where they exist.
+---
+---
+--- Functions that accept a callback are asynchronous. These functions may
+--- immediately return results to the caller to indicate their initial status, but
+--- their final execution is deferred until at least the next libuv loop iteration.
+--- After completion, their callbacks are executed with any results passed to it.
+---
+--- Functions that do not accept a callback are synchronous. These functions
+--- immediately return their results to the caller.
+---
+--- Some (generally FS and DNS) functions can behave either synchronously or
+--- asynchronously. If a callback is provided to these functions, they behave
+--- asynchronously; if no callback is provided, they behave synchronously.
+---
+---
+--- Some unique types are defined. These are not actual types in Lua, but they are
+--- used here to facilitate documenting consistent behavior:
+--- - `fail`: an assertable `nil, string, string` tuple (see Error handling)
+--- - `callable`: a `function`; or a `table` or `userdata` with a `__call`
+---   metamethod
+--- - `buffer`: a `string` or a sequential `table` of `string`s
+--- - `threadargs`: variable arguments (`...`) of type `nil`, `boolean`, `number`,
+---   `string`, or `userdata`
+---
+---
+--- This documentation is mostly a retelling of the libuv API documentation
+--- within the context of luv's Lua API. Low-level implementation details and
+--- unexposed C functions and types are not documented here except for when they
+--- are relevant to behavior seen in the Lua module.
+---
+--- [Error handling]: #error-handling
+---
+--- In libuv, errors are negative numbered constants; however, these errors and the
+--- functions used to handle them are not exposed to luv users. Instead, if an
+--- internal error is encountered, the luv function will return to the caller an
+--- assertable `nil, err, name` tuple.
+---
+--- - `nil` idiomatically indicates failure
+--- - `err` is a string with the format `{name}: {message}`
+---   - `{name}` is the error name provided internally by `uv_err_name`
+---   - `{message}` is a human-readable message provided internally by `uv_strerror`
+--- - `name` is the same string used to construct `err`
+---
+--- This tuple is referred to below as the `fail` pseudo-type.
+---
+--- When a function is called successfully, it will return either a value that is
+--- relevant to the operation of the function, or the integer `0` to indicate
+--- success, or sometimes nothing at all. These cases are documented below.
+---
+---
+--- [reference counting]: #reference-counting
+---
+--- The libuv event loop (if run in the default mode) will run until there are no
+--- active and referenced handles left. The user can force the loop to exit early by
+--- unreferencing handles which are active, for example by calling `uv.unref()`
+--- after calling `uv.timer_start()`.
+---
+--- A handle can be referenced or unreferenced, the refcounting scheme doesn't use a
+--- counter, so both operations are idempotent.
+---
+--- All handles are referenced when active by default, see `uv.is_active()` for a
+--- more detailed explanation on what being active involves.
+---
+---
+--- [File system operations]: #file-system-operations
+---
+--- Most file system functions can operate synchronously or asynchronously. When a synchronous version is called (by omitting a callback), the function will
+--- immediately return the results of the FS call. When an asynchronous version is
+--- called (by providing a callback), the function will immediately return a
+--- `uv_fs_t userdata` and asynchronously execute its callback; if an error is encountered, the first and only argument passed to the callback will be the `err` error string; if the operation completes successfully, the first argument will be `nil` and the remaining arguments will be the results of the FS call.
+---
+--- Synchronous and asynchronous versions of `readFile` (with naive error handling)
+--- are implemented below as an example:
+---
+--- ```lua
+--- local function readFileSync(path)
+---   local fd = assert(uv.fs_open(path, "r", 438))
+---   local stat = assert(uv.fs_fstat(fd))
+---   local data = assert(uv.fs_read(fd, stat.size, 0))
+---   assert(uv.fs_close(fd))
+---   return data
+--- end
+---
+--- local data = readFileSync("main.lua")
+--- print("synchronous read", data)
+--- ```
+---
+--- ```lua
+--- local function readFile(path, callback)
+---   uv.fs_open(path, "r", 438, function(err, fd)
+---     assert(not err, err)
+---     uv.fs_fstat(fd, function(err, stat)
+---       assert(not err, err)
+---       uv.fs_read(fd, stat.size, 0, function(err, data)
+---         assert(not err, err)
+---         uv.fs_close(fd, function(err)
+---           assert(not err, err)
+---           return callback(data)
+---         end)
+---       end)
+---     end)
+---   end)
+--- end
+---
+--- readFile("main.lua", function(data)
+---   print("asynchronous read", data)
+--- end)
+--- ```
+---
+---
+--- [Thread pool work scheduling]: #thread-pool-work-scheduling
+---
+--- Libuv provides a threadpool which can be used to run user code and get notified
+--- in the loop thread. This threadpool is internally used to run all file system
+--- operations, as well as `getaddrinfo` and `getnameinfo` requests.
+---
+--- ```lua
+--- local function work_callback(a, b)
+---   return a + b
+--- end
+---
+--- local function after_work_callback(c)
+---   print("The result is: " .. c)
+--- end
+---
+--- local work = uv.new_work(work_callback, after_work_callback)
+---
+--- work:queue(1, 2)
+---
+--- -- output: "The result is: 3"
+--- ```
+---
+---
+--- [DNS utility functions]: #dns-utility-functions
+---
+---
+--- [Threading and synchronization utilities]: #threading-and-synchronization-utilities
+---
+--- Libuv provides cross-platform implementations for multiple threading an
+---  synchronization primitives. The API largely follows the pthreads API.
+---
+---
+--- [Miscellaneous utilities]: #miscellaneous-utilities
+---
+---
+--- [Metrics operations]: #metrics-operations
+---
+---@class uv
+---
+---@field errno uv.errno
+---
+local uv
+
+
+--- This call is used in conjunction with `uv.listen()` to accept incoming
+--- connections. Call this function after receiving a callback to accept the
+--- connection.
+---
+--- When the connection callback is called it is guaranteed that this function
+--- will complete successfully the first time. If you attempt to use it more than
+--- once, it may fail. It is suggested to only call this function once per
+--- connection call.
+---
+--- ```lua
+--- server:listen(128, function (err)
+---   local client = uv.new_tcp()
+---   server:accept(client)
+--- end)
+--- ```
+---
+---@param  stream        uv.uv_stream_t
+---@param  client_stream uv.uv_stream_t
+---@return 0|nil         success
+---@return uv.error.message|nil    err
+---@return uv.error.name|nil    err_name
+function uv.accept(stream, client_stream) end
+
+
+--- Wakeup the event loop and call the async handle's callback.
+---
+--- **Note**: It's safe to call this function from any thread. The callback will be
+--- called on the loop thread.
+---
+--- **Warning**: libuv will coalesce calls to `uv.async_send(async)`, that is, not
+--- every call to it will yield an execution of the callback. For example: if
+--- `uv.async_send()` is called 5 times in a row before the callback is called, the
+--- callback will only be called once. If `uv.async_send()` is called again after
+--- the callback was called, it will be called again.
+---
+---@param  async      uv.uv_async_t
+---@param  ...        uv.threadargs
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.async_send(async, ...) end
+
+
+
+--- Returns an estimate of the default amount of parallelism a program should use. Always returns a non-zero value.
+---
+--- On Linux, inspects the calling thread’s CPU affinity mask to determine if it has been pinned to specific CPUs.
+---
+--- On Windows, the available parallelism may be underreported on systems with more than 64 logical CPUs.
+---
+--- On other platforms, reports the number of CPUs that the operating system considers to be online.
+---
+---@return integer
+function uv.available_parallelism() end
+
+
+
+--- Get backend file descriptor. Only kqueue, epoll, and event ports are supported.
+---
+--- This can be used in conjunction with `uv.run("nowait")` to poll in one thread
+--- and run the event loop's callbacks in another
+---
+--- **Note**: Embedding a kqueue fd in another kqueue pollset doesn't work on all
+--- platforms. It's not an error to add the fd but it never generates events.
+---
+---@return integer|nil fd
+function uv.backend_fd() end
+
+
+
+--- Get the poll timeout. The return value is in milliseconds, or -1 for no timeout.
+---
+---@return integer
+function uv.backend_timeout() end
+
+
+--- Cancel a pending request. Fails if the request is executing or has finished
+--- executing. Only cancellation of `uv_fs_t`, `uv_getaddrinfo_t`,
+--- `uv_getnameinfo_t` and `uv_work_t` requests is currently supported.
+---
+---@param  req        uv.uv_req_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.cancel(req) end
+
+
+
+--- Sets the current working directory with the string `cwd`.
+---
+---@param  cwd        string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.chdir(cwd) end
+
+
+--- Start the handle with the given callback.
+---
+---@param  check      uv.uv_check_t
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.check_start(check, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param  check      uv.uv_check_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.check_stop(check) end
+
+
+--- Request handle to be closed. `callback` will be called asynchronously after this
+--- call. This MUST be called on each handle before memory is released.
+---
+--- Handles that wrap file descriptors are closed immediately but `callback` will
+--- still be deferred to the next iteration of the event loop. It gives you a chance
+--- to free up any resources associated with the handle.
+---
+--- In-progress requests, like `uv_connect_t` or `uv_write_t`, are cancelled and
+--- have their callbacks called asynchronously with `ECANCELED`.
+---
+---@param handle uv.uv_handle_t
+---@param callback? function
+function uv.close(handle, callback) end
+
+
+
+--- Returns information about the CPU(s) on the system as a table of tables for each
+--- CPU found.
+---
+--- **Returns:** `table` or `fail`
+--- - `[1, 2, 3, ..., n]` : `table`
+---   - `model` : `string`
+---   - `speed` : `number`
+---   - `times` : `table`
+---     - `user` : `number`
+---     - `nice` : `number`
+---     - `sys` : `number`
+---     - `idle` : `number`
+---     - `irq` : `number`
+---
+---@return uv.cpu_info.cpu[]|nil info
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.cpu_info() end
+
+
+
+--- Returns the current working directory.
+---
+---@return string|nil cwd
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.cwd() end
+
+
+
+--- Disables inheritance for file descriptors / handles that this process inherited
+--- from its parent. The effect is that child processes spawned by this process
+--- don't accidentally inherit these handles.
+---
+--- It is recommended to call this function as early in your program as possible,
+--- before the inherited file descriptors can be closed or duplicated.
+---
+--- **Note:** This function works on a best-effort basis: there is no guarantee that
+--- libuv can discover all file descriptors that were inherited. In general it does
+--- a better job on Windows than it does on Unix.
+function uv.disable_stdio_inheritance() end
+
+
+
+--- Returns the executable path.
+---
+---@return string|nil exepath
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.exepath() end
+
+
+--- Gets the platform dependent file descriptor equivalent.
+---
+--- The following handles are supported: TCP, pipes, TTY, UDP and poll. Passing any
+--- other handle type will fail with `EINVAL`.
+---
+--- If a handle doesn't have an attached file descriptor yet or the handle itself
+--- has been closed, this function will return `EBADF`.
+---
+--- **Warning**: Be very careful when using this function. libuv assumes it's in
+--- control of the file descriptor so any change to it may lead to malfunction.
+---
+---@param  handle      uv.uv_handle_t
+---@return integer|nil fileno
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fileno(handle) end
+
+
+
+--- Equivalent to `access(2)` on Unix. Windows uses `GetFileAttributesW()`. Access
+--- `mode` can be an integer or a string containing `"R"` or `"W"` or `"X"`.
+--- Returns `true` or `false` indicating access permission.
+---
+---@param  path        string
+---@param  mode        integer|string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, mode:integer|string, callback:uv.fs_access.callback):uv.uv_fs_t
+function uv.fs_access(path, mode) end
+
+
+
+--- Equivalent to `chmod(2)`.
+---
+---@param  path        string
+---@param  mode        integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, mode:integer, callback:uv.fs_chmod.callback):uv.uv_fs_t
+function uv.fs_chmod(path, mode) end
+
+
+
+--- Equivalent to `chown(2)`.
+---
+---@param  path        string
+---@param  uid         integer
+---@param  gid         integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, uid:integer, gid:integer, callback:uv.fs_chown.callback):uv.uv_fs_t
+function uv.fs_chown(path, uid, gid) end
+
+
+
+--- Equivalent to `close(2)`.
+---
+---@param  fd          integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, callback:uv.fs_close.callback):uv.uv_fs_t
+function uv.fs_close(fd) end
+
+
+--- Closes a directory stream returned by a successful `uv.fs_opendir()` call.
+---
+---@param  dir         uv.luv_dir_t
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(dir:uv.luv_dir_t, callback:uv.fs_closedir.callback):uv.uv_fs_t
+function uv.fs_closedir(dir) end
+
+
+
+--- Copies a file from path to new_path. If the `flags` parameter is omitted, then the 3rd parameter will be treated as the `callback`.
+---
+--- **Returns (sync version):** `boolean` or `fail`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param  path        string
+---@param  new_path    string
+---@param  flags?      uv.fs_copyfile.flags
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, new_path:string, flags?:uv.fs_copyfile.flags, callback:uv.fs_copyfile.callback):uv.uv_fs_t
+---@overload fun(path:string, new_path:string, callback:uv.fs_copyfile.callback):uv.uv_fs_t
+function uv.fs_copyfile(path, new_path, flags) end
+
+
+--- Get the path being monitored by the handle.
+---
+---@param  fs_event   uv.uv_fs_event_t
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_event_getpath(fs_event) end
+
+
+--- Start the handle with the given callback, which will watch the specified path
+--- for changes.
+---
+---@param  fs_event   uv.uv_fs_event_t
+---@param  path       string
+---@param  flags      uv.fs_event_start.flags
+---@param  callback   uv.fs_event_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_event_start(fs_event, path, flags, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param  fs_event   uv.uv_fs_event_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_event_stop(fs_event) end
+
+
+
+--- Equivalent to `fchmod(2)`.
+---
+---@param  fd          integer
+---@param  mode        integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, mode:integer, callback:uv.fs_fchmod.callback):uv.uv_fs_t
+function uv.fs_fchmod(fd, mode) end
+
+
+
+--- Equivalent to `fchown(2)`.
+---
+---@param  fd          integer
+---@param  uid         integer
+---@param  gid         integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, uid:integer, gid:integer, callback:uv.fs_fchown.callback):uv.uv_fs_t
+function uv.fs_fchown(fd, uid, gid) end
+
+
+
+--- Equivalent to `fdatasync(2)`.
+---
+---@param  fd          integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, callback:uv.fs_fdatasync.callback):uv.uv_fs_t
+function uv.fs_fdatasync(fd) end
+
+
+
+--- Equivalent to `fstat(2)`.
+---
+--- **Returns (sync version):** `table` or `fail` (see `uv.fs_stat`)
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param  fd                    integer
+---@return uv.fs_stat.result|nil stat
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, callback:uv.fs_fstat.callback):uv.uv_fs_t
+function uv.fs_fstat(fd) end
+
+
+
+--- Equivalent to `fsync(2)`.
+---
+---@param  fd          integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, callback:uv.fs_fsync.callback):uv.uv_fs_t
+function uv.fs_fsync(fd) end
+
+
+
+--- Equivalent to `ftruncate(2)`.
+---
+---@param  fd          integer
+---@param  offset      integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, offset:integer, callback:uv.fs_ftruncate.callback):uv.uv_fs_t
+function uv.fs_ftruncate(fd, offset) end
+
+
+
+--- Equivalent to `futime(2)`.
+---
+---@param  fd          integer
+---@param  atime       number
+---@param  mtime       number
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, atime:number, mtime:number, callback:uv.fs_futime.callback):uv.uv_fs_t
+function uv.fs_futime(fd, atime, mtime) end
+
+
+
+--- Equivalent to `lchown(2)`.
+---
+---@param  fd          integer
+---@param  uid         integer
+---@param  gid         integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, uid:integer, gid:integer, callback:uv.fs_lchown.callback):uv.uv_fs_t
+function uv.fs_lchown(fd, uid, gid) end
+
+
+
+--- Equivalent to `link(2)`.
+---
+--- **Returns (sync version):** `boolean` or `fail`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param  path        string
+---@param  new_path    string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, new_path:string, callback:uv.fs_link.callback):uv.uv_fs_t
+function uv.fs_link(path, new_path) end
+
+
+
+--- Equivalent to `lstat(2)`.
+---
+--- **Returns (sync version):** `table` or `fail` (see `uv.fs_stat`)
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param  path                  integer
+---@return uv.fs_stat.result|nil stat
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:integer, callback:uv.fs_lstat.callback):uv.uv_fs_t
+function uv.fs_lstat(path) end
+
+
+
+--- Equivalent to `lutime(2)`.
+---
+---@param  path        string
+---@param  atime       number
+---@param  mtime       number
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, atime:number, mtime:number, callback:uv.fs_lutime.callback):uv.uv_fs_t
+function uv.fs_lutime(path, atime, mtime) end
+
+
+
+--- Equivalent to `mkdir(2)`.
+---
+---@param  path        string
+---@param  mode        integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, mode:integer, callback:uv.fs_mkdir.callback):uv.uv_fs_t
+function uv.fs_mkdir(path, mode) end
+
+
+
+--- Equivalent to `mkdtemp(3)`.
+---
+---@param  template   string
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(template:string, callback:uv.fs_mkdtemp.callback):uv.uv_fs_t
+function uv.fs_mkdtemp(template) end
+
+
+
+--- Equivalent to `mkstemp(3)`. Returns a temporary file handle and filename.
+---
+---@param  template    string
+---@return integer|nil fd
+---@return string      path_or_errmsg
+---@return uv.error.name|nil err_name
+---
+---@overload fun(template:string, callback:uv.fs_mkstemp.callback):uv.uv_fs_t
+function uv.fs_mkstemp(template) end
+
+
+
+--- Equivalent to `open(2)`. Access `flags` may be an integer or one of: `"r"`,
+--- `"rs"`, `"sr"`, `"r+"`, `"rs+"`, `"sr+"`, `"w"`, `"wx"`, `"xw"`, `"w+"`,
+--- `"wx+"`, `"xw+"`, `"a"`, `"ax"`, `"xa"`, `"a+"`, `"ax+"`, or "`xa+`".
+---
+--- **Returns (sync version):** `integer` or `fail`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+--- **Note:** On Windows, libuv uses `CreateFileW` and thus the file is always
+--- opened in binary mode. Because of this, the `O_BINARY` and `O_TEXT` flags are
+--- not supported.
+---
+---@param  path        string
+---@param  flags       uv.fs_open.flags
+---@param  mode        integer
+---@return integer|nil fd
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, flags:uv.fs_open.flags, mode:integer, callback:uv.fs_open.callback):uv.uv_fs_t
+function uv.fs_open(path, flags, mode) end
+
+
+
+--- Opens path as a directory stream. Returns a handle that the user can pass to
+--- `uv.fs_readdir()`. The `entries` parameter defines the maximum number of entries
+--- that should be returned by each call to `uv.fs_readdir()`.
+---
+--- **Returns (sync version):** `luv_dir_t userdata` or `fail`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param  path             string
+---@return uv.luv_dir_t|nil dir
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path: string, callback: uv.fs_opendir.callback, entries?: integer):uv.uv_fs_t
+function uv.fs_opendir(path) end
+
+
+--- Get the path being monitored by the handle.
+---
+---@param  fs_poll    uv.uv_fs_poll_t
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_poll_getpath(fs_poll) end
+
+
+--- Check the file at `path` for changes every `interval` milliseconds.
+---
+--- **Note:** For maximum portability, use multi-second intervals. Sub-second
+--- intervals will not detect all changes on many file systems.
+---
+---@param  fs_poll    uv.uv_fs_poll_t
+---@param  path       string
+---@param  interval   integer
+---@param  callback   uv.fs_poll_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_poll_start(fs_poll, path, interval, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param fs_poll uv.uv_fs_poll_t
+---@return 0|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_poll_stop(fs_poll) end
+
+
+
+--- Equivalent to `preadv(2)`. Returns any data. An empty string indicates EOF.
+---
+--- If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and update the current file offset.'
+---
+--- **Note:** When `offset` is >= 0, the current file offset will not be updated by the read.
+---
+---@param  fd         integer
+---@param  size       integer
+---@param  offset?    integer
+---@return string|nil data
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, size:integer, offset?:integer, callback:uv.fs_read.callback):uv.uv_fs_t
+function uv.fs_read(fd, size, offset) end
+
+
+--- Iterates over the directory stream `luv_dir_t` returned by a successful
+--- `uv.fs_opendir()` call. A table of data tables is returned where the number
+--- of entries `n` is equal to or less than the `entries` parameter used in
+--- the associated `uv.fs_opendir()` call.
+---
+---@param  dir                       uv.luv_dir_t
+---@return uv.fs_readdir.entry[]|nil entries
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(dir:uv.luv_dir_t, callback:uv.fs_readdir.callback):uv.uv_fs_t
+function uv.fs_readdir(dir) end
+
+
+
+--- Equivalent to `readlink(2)`.
+---
+---@param  path       string
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, callback:uv.fs_readlink.callback):uv.uv_fs_t
+function uv.fs_readlink(path) end
+
+
+
+--- Equivalent to `realpath(3)`.
+---
+---@param  path       string
+---@return string|nil realpath
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, callback:uv.fs_realpath.callback):uv.uv_fs_t
+function uv.fs_realpath(path) end
+
+
+
+--- Equivalent to `rename(2)`.
+---
+---@param  path        string
+---@param  new_path    string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, new_path:string, callback:uv.fs_rename.callback):uv.uv_fs_t
+function uv.fs_rename(path, new_path) end
+
+
+
+--- Equivalent to `rmdir(2)`.
+---
+---@param  path        string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, callback:uv.fs_rmdir.callback):uv.uv_fs_t
+function uv.fs_rmdir(path) end
+
+
+
+--- Equivalent to `scandir(3)`, with a slightly different API. Returns a handle that
+--- the user can pass to `uv.fs_scandir_next()`.
+---
+--- **Note:** This function can be used synchronously or asynchronously. The request
+--- userdata is always synchronously returned regardless of whether a callback is
+--- provided and the same userdata is passed to the callback if it is provided.
+---
+---@param  path           string
+---@param  callback?      uv.fs_scandir.callback
+---@return uv.uv_fs_t|nil fs
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.fs_scandir(path, callback) end
+
+
+
+--- Called on a `uv_fs_t` returned by `uv.fs_scandir()` to get the next directory
+--- entry data as a `name, type` pair. When there are no more entries, `nil` is
+--- returned.
+---
+--- **Note:** This function only has a synchronous version. See `uv.fs_opendir` and
+--- its related functions for an asynchronous version.
+---
+---@param  fs         uv.uv_fs_t
+---@return string|nil name
+---@return string     type_or_errmsg
+---@return uv.error.name|nil err_name
+function uv.fs_scandir_next(fs) end
+
+
+
+--- Limited equivalent to `sendfile(2)`. Returns the number of bytes written.
+---
+---@param  out_fd      integer
+---@param  in_fd       integer
+---@param  in_offset   integer
+---@param  size        integer
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(out_fd:integer, in_fd:integer, in_offset:integer, size:integer, callback:uv.fs_sendfile.callback):uv.uv_fs_t
+function uv.fs_sendfile(out_fd, in_fd, in_offset, size) end
+
+
+
+--- Equivalent to `stat(2)`.
+---
+--- **Returns (sync version):** `table` or `fail`
+--- - `dev` : `integer`
+--- - `mode` : `integer`
+--- - `nlink` : `integer`
+--- - `uid` : `integer`
+--- - `gid` : `integer`
+--- - `rdev` : `integer`
+--- - `ino` : `integer`
+--- - `size` : `integer`
+--- - `blksize` : `integer`
+--- - `blocks` : `integer`
+--- - `flags` : `integer`
+--- - `gen` : `integer`
+--- - `atime` : `table`
+---   - `sec` : `integer`
+---   - `nsec` : `integer`
+--- - `mtime` : `table`
+---   - `sec` : `integer`
+---   - `nsec` : `integer`
+--- - `ctime` : `table`
+---   - `sec` : `integer`
+---   - `nsec` : `integer`
+--- - `birthtime` : `table`
+---   - `sec` : `integer`
+---   - `nsec` : `integer`
+--- - `type` : `string`
+---
+--- **Returns (async version):** `uv_fs_t userdata`
+---
+---@param path string
+---@return uv.fs_stat.result|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, callback:uv.fs_stat.callback):uv.uv_fs_t
+function uv.fs_stat(path) end
+
+
+--- Equivalent to `statfs(2)`.
+---
+--- **Returns** `table` or `nil`
+--- - `type` : `integer`
+--- - `bsize` : `integer`
+--- - `blocks` : `integer`
+--- - `bfree` : `integer`
+--- - `bavail` : `integer`
+--- - `files` : `integer`
+--- - `ffree` : `integer`
+---
+---@param path string
+---@return uv.fs_statfs.result|nil stat
+---
+---@overload fun(path: string, callback: uv.fs_statfs.callback)
+function uv.fs_statfs(path) end
+
+
+
+--- Equivalent to `symlink(2)`. If the `flags` parameter is omitted, then the 3rd parameter will be treated as the `callback`.
+---
+---
+---@param  path        string
+---@param  new_path    string
+---@param  flags?      uv.fs_symlink.flags|integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, new_path:string, flags?:uv.fs_symlink.flags|integer, callback:uv.fs_symlink.callback):uv.uv_fs_t
+---@overload fun(path:string, new_path:string, callback:uv.fs_symlink.callback):uv.uv_fs_t
+function uv.fs_symlink(path, new_path, flags) end
+
+
+
+--- Equivalent to `unlink(2)`.
+---
+---@param  path        string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, callback:uv.fs_unlink.callback):uv.uv_fs_t
+function uv.fs_unlink(path) end
+
+
+
+--- Equivalent to `utime(2)`.
+---
+---@param  path        string
+---@param  atime       number
+---@param  mtime       number
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(path:string, atime:number, mtime:number, callback:uv.fs_utime.callback):uv.uv_fs_t
+function uv.fs_utime(path, atime, mtime) end
+
+
+
+--- Equivalent to `pwritev(2)`. Returns the number of bytes written.
+---
+--- If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and update the current file offset.'
+---
+--- **Note:** When `offset` is >= 0, the current file offset will not be updated by the write.
+---
+---@param  fd          integer
+---@param  data        uv.buffer
+---@param  offset?     integer
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(fd:integer, data:uv.buffer, offset?:integer, callback:uv.fs_write.callback):uv.uv_fs_t
+function uv.fs_write(fd, data, offset) end
+
+
+
+--- Gets the amount of memory available to the process in bytes based on limits
+--- imposed by the OS. If there is no such constraint, or the constraint is unknown,
+--- 0 is returned. Note that it is not unusual for this value to be less than or
+--- greater than the total system memory.
+---
+---@return number
+function uv.get_constrained_memory() end
+
+
+
+--- Returns the current free system memory in bytes.
+---
+---@return number
+function uv.get_free_memory() end
+
+
+
+--- Returns the title of the current process.
+---
+---@return string|nil title
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.get_process_title() end
+
+
+
+--- Returns the current total system memory in bytes.
+---
+--- **Returns:** `number`
+---
+---@return number
+function uv.get_total_memory() end
+
+
+
+--- Equivalent to `getaddrinfo(3)`. Either `node` or `service` may be `nil` but not
+--- both.
+---
+--- Valid hint strings for the keys that take a string:
+--- - `family`: `"unix"`, `"inet"`, `"inet6"`, `"ipx"`,
+--- `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, or `"packet"`
+--- - `socktype`: `"stream"`, `"dgram"`, `"raw"`,
+--- `"rdm"`, or `"seqpacket"`
+--- - `protocol`: will be looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc)
+---
+--- **Returns (sync version):** `table` or `fail`
+--- - `[1, 2, 3, ..., n]` : `table`
+---   - `addr` : `string`
+---   - `family` : `string`
+---   - `port` : `integer` or `nil`
+---   - `socktype` : `string`
+---   - `protocol` : `string`
+---   - `canonname` : `string` or `nil`
+---
+--- **Returns (async version):** `uv_getaddrinfo_t userdata` or `fail`
+---
+---@param  host                        string
+---@param  service                     string
+---@param  hints?                      uv.getaddrinfo.hints
+---@return uv.getaddrinfo.result[]|nil info
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(host:string, service:string, hints?:uv.getaddrinfo.hints, callback:uv.getaddrinfo.callback):uv.uv_getaddrinfo_t userdata|nil, string?, string?
+function uv.getaddrinfo(host, service, hints) end
+
+
+
+--- Returns the group ID of the process.
+---
+--- **Note:** This is not a libuv function and is not supported on Windows.
+---
+---@return integer
+function uv.getgid() end
+
+
+
+--- Equivalent to `getnameinfo(3)`.
+---
+--- When specified, `family` must be one of `"unix"`, `"inet"`, `"inet6"`, `"ipx"`,
+--- `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, or `"packet"`.
+---
+--- **Returns (sync version):** `string, string` or `fail`
+---
+--- **Returns (async version):** `uv_getnameinfo_t userdata` or `fail`
+---
+---@param  address    uv.getnameinfo.address
+---@return string|nil host
+---@return string     service_or_errmsg
+---@return uv.error.name|nil err_name
+---
+---@overload fun(address:uv.getnameinfo.address, callback:uv.getnameinfo.callback):uv.uv_getnameinfo_t|nil, string|nil, string|nil
+function uv.getnameinfo(address) end
+
+
+
+--- **Deprecated:** Please use `uv.os_getpid()` instead.
+---
+function uv.getpid() end
+
+
+
+--- Returns the resource usage.
+---
+--- **Returns:** `table` or `fail`
+--- - `utime` : `table` (user CPU time used)
+---   - `sec` : `integer`
+---   - `usec` : `integer`
+--- - `stime` : `table` (system CPU time used)
+---   - `sec` : `integer`
+---   - `usec` : `integer`
+--- - `maxrss` : `integer` (maximum resident set size)
+--- - `ixrss` : `integer` (integral shared memory size)
+--- - `idrss` : `integer` (integral unshared data size)
+--- - `isrss` : `integer` (integral unshared stack size)
+--- - `minflt` : `integer` (page reclaims (soft page faults))
+--- - `majflt` : `integer` (page faults (hard page faults))
+--- - `nswap` : `integer` (swaps)
+--- - `inblock` : `integer` (block input operations)
+--- - `oublock` : `integer` (block output operations)
+--- - `msgsnd` : `integer` (IPC messages sent)
+--- - `msgrcv` : `integer` (IPC messages received)
+--- - `nsignals` : `integer` (signals received)
+--- - `nvcsw` : `integer` (voluntary context switches)
+--- - `nivcsw` : `integer` (involuntary context switches)
+---
+---@return uv.getrusage.result|nil usage
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.getrusage() end
+
+
+
+--- Cross-platform implementation of `gettimeofday(2)`. Returns the seconds and
+--- microseconds of a unix time as a pair.
+---
+---@return integer|nil    seconds
+---@return integer|string usecs_or_errmsg
+---@return uv.error.name|nil err_name
+function uv.gettimeofday() end
+
+
+
+--- Returns the user ID of the process.
+---
+--- **Note:** This is not a libuv function and is not supported on Windows.
+---
+---@return integer
+function uv.getuid() end
+
+
+
+--- Used to detect what type of stream should be used with a given file
+--- descriptor `fd`. Usually this will be used during initialization to guess the
+--- type of the stdio streams.
+---
+---@param fd integer
+---@return string
+function uv.guess_handle(fd) end
+
+
+--- Returns the name of the struct for a given handle (e.g. `"pipe"` for `uv_pipe_t`)
+--- and the libuv enum integer for the handle's type (`uv_handle_type`).
+---
+---@param  handle  uv.uv_handle_t
+---@return string  type
+---@return integer enum
+function uv.handle_get_type(handle) end
+
+
+--- Returns `true` if the handle referenced, `false` if not.
+---
+--- See [Reference counting][].
+---
+---@param  handle      uv.uv_handle_t
+---@return boolean|nil has_ref
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.has_ref(handle) end
+
+
+
+--- Returns a current high-resolution time in nanoseconds as a number. This is
+--- relative to an arbitrary time in the past. It is not related to the time of day
+--- and therefore not subject to clock drift. The primary use is for measuring
+--- time between intervals.
+---
+--- **Returns:** `number`
+---
+---@return number
+function uv.hrtime() end
+
+
+--- Start the handle with the given callback.
+---
+---@param  idle       uv.uv_idle_t
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.idle_start(idle, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param  idle       uv.uv_idle_t
+---@param  check      any
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.idle_stop(idle, check) end
+
+
+
+--- Retrieves a network interface identifier suitable for use in an IPv6 scoped
+--- address. On Windows, returns the numeric `ifindex` as a string. On all other
+--- platforms, `uv.if_indextoname()` is used.
+---
+---@param  ifindex    integer
+---@return string|nil id
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.if_indextoiid(ifindex) end
+
+
+
+--- IPv6-capable implementation of `if_indextoname(3)`.
+---
+---@param  ifindex    integer
+---@return string|nil name
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.if_indextoname(ifindex) end
+
+
+--- Returns address information about the network interfaces on the system in a
+--- table. Each table key is the name of the interface while each associated value
+--- is an array of address information where fields are `ip`, `family`, `netmask`,
+--- `internal`, and `mac`.
+---
+---@return table<string, uv.interface_addresses.addr>
+function uv.interface_addresses() end
+
+
+--- Returns `true` if the handle is active, `false` if it's inactive. What "active”
+--- means depends on the type of handle:
+---
+---   - A [`uv_async_t`][] handle is always active and cannot be deactivated, except
+---   by closing it with `uv.close()`.
+---
+---   - A [`uv_pipe_t`][], [`uv_tcp_t`][], [`uv_udp_t`][], etc. handle - basically
+---   any handle that deals with I/O - is active when it is doing something that
+---   involves I/O, like reading, writing, connecting, accepting new connections,
+---   etc.
+---
+---   - A [`uv_check_t`][], [`uv_idle_t`][], [`uv_timer_t`][], etc. handle is active
+---   when it has been started with a call to `uv.check_start()`, `uv.idle_start()`,
+---   `uv.timer_start()` etc. until it has been stopped with a call to its
+---   respective stop function.
+---
+---@param  handle      uv.uv_handle_t
+---@return boolean|nil active
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.is_active(handle) end
+
+
+--- Returns `true` if the handle is closing or closed, `false` otherwise.
+---
+--- **Note**: This function should only be used between the initialization of the
+--- handle and the arrival of the close callback.
+---
+---@param  handle      uv.uv_handle_t
+---@return boolean|nil closing
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.is_closing(handle) end
+
+
+--- Returns `true` if the stream is readable, `false` otherwise.
+---
+---@param stream uv.uv_stream_t
+---@return boolean
+function uv.is_readable(stream) end
+
+
+--- Returns `true` if the stream is writable, `false` otherwise.
+---
+---@param stream uv.uv_stream_t
+---@return boolean
+function uv.is_writable(stream) end
+
+
+--- Sends the specified signal to the given PID. Check the documentation on
+--- `uv_signal_t` for signal support, specially on Windows.
+---
+---@param  pid        integer
+---@param  signum     integer|string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.kill(pid, signum) end
+
+
+--- Start listening for incoming connections. `backlog` indicates the number of
+--- connections the kernel might queue, same as `listen(2)`. When a new incoming
+--- connection is received the callback is called.
+---
+--- **Returns:** `0` or `fail`
+---
+---@param  stream     uv.uv_stream_t
+---@param  backlog    integer
+---@param  callback   uv.listen.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.listen(stream, backlog, callback) end
+
+
+--- Returns the load average as a triad. Not supported on Windows.
+---
+---@return number
+---@return number
+---@return number
+function uv.loadavg() end
+
+
+
+--- Returns `true` if there are referenced active handles, active requests, or
+--- closing handles in the loop; otherwise, `false`.
+---
+---@return boolean|nil alive
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.loop_alive() end
+
+
+
+--- Closes all internal loop resources. In normal execution, the loop will
+--- automatically be closed when it is garbage collected by Lua, so it is not
+--- necessary to explicitly call `loop_close()`. Call this function only after the
+--- loop has finished executing and all open handles and requests have been closed,
+--- or it will return `EBUSY`.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.loop_close() end
+
+
+
+--- Set additional loop options. You should normally call this before the first call
+--- to uv_run() unless mentioned otherwise.
+---
+--- Supported options:
+---
+---   - `"block_signal"`: Block a signal when polling for new events. The second argument
+---   to loop_configure() is the signal name (as a lowercase string) or the signal number.
+---   This operation is currently only implemented for `"sigprof"` signals, to suppress
+---   unnecessary wakeups when using a sampling profiler. Requesting other signals will
+---   fail with `EINVAL`.
+---   - `"metrics_idle_time"`: Accumulate the amount of idle time the event loop spends
+---   in the event provider. This option is necessary to use `metrics_idle_time()`.
+---
+--- An example of a valid call to this function is:
+---
+--- ```lua
+--- uv.loop_configure("block_signal", "sigprof")
+--- ```
+---
+--- **Note:** Be prepared to handle the `ENOSYS` error; it means the loop option is
+--- not supported by the platform.
+---
+---@param  option     "block_signal"
+---@param  value      "sigprof"
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(option: "metrics_idle_time"):(success:0|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+function uv.loop_configure(option, value) end
+
+
+--- If the loop is running, returns a string indicating the mode in use. If the loop
+--- is not running, `nil` is returned instead.
+---
+---@return string|nil
+function uv.loop_mode() end
+
+
+--- Retrieve the amount of time the event loop has been idle in the kernel’s event
+--- provider (e.g. `epoll_wait`). The call is thread safe.
+---
+--- The return value is the accumulated time spent idle in the kernel’s event
+--- provider starting from when the [`uv_loop_t`][] was configured to collect the idle time.
+---
+--- **Note:** The event loop will not begin accumulating the event provider’s idle
+--- time until calling `loop_configure` with `"metrics_idle_time"`.
+---
+--- **Returns:** `number`
+---
+--- ---
+---
+--- [luv]: https://github.com/luvit/luv
+--- [luvit]: https://github.com/luvit/luvit
+--- [libuv]: https://github.com/libuv/libuv
+--- [libuv documentation page]: http://docs.libuv.org/
+--- [libuv API documentation]: http://docs.libuv.org/en/v1.x/api.html
+---
+---@return number
+function uv.metrics_idle_time() end
+
+
+--- Creates and initializes a new `uv_async_t`. Returns the Lua userdata wrapping
+--- it. A `nil` callback is allowed.
+---
+--- **Note**: Unlike other handle initialization functions, this immediately starts
+--- the handle.
+---
+---@param  callback?         uv.new_async.callback
+---@return uv.uv_async_t|nil async
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_async(callback) end
+
+
+--- Creates and initializes a new `uv_check_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_check_t|nil check
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_check() end
+
+
+--- Creates and initializes a new `uv_fs_event_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_fs_event_t|nil fs_event
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_fs_event() end
+
+
+--- Creates and initializes a new `uv_fs_poll_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_fs_poll_t|nil fs_poll
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_fs_poll() end
+
+
+--- Creates and initializes a new `uv_idle_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_idle_t|nil idle
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_idle() end
+
+
+--- Creates and initializes a new `uv_pipe_t`. Returns the Lua userdata wrapping
+--- it. The `ipc` argument is a boolean to indicate if this pipe will be used for
+--- handle passing between processes.
+---
+---@param  ipc?             boolean|false
+---@return uv.uv_pipe_t|nil pipe
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_pipe(ipc) end
+
+
+--- Initialize the handle using a file descriptor.
+---
+--- The file descriptor is set to non-blocking mode.
+---
+---@param  fd               integer
+---@return uv.uv_poll_t|nil poll
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_poll(fd) end
+
+
+--- Creates and initializes a new `uv_prepare_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_prepare_t|nil prepare
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_prepare() end
+
+
+--- Creates and initializes a new `uv_signal_t`. Returns the Lua userdata wrapping
+--- it.
+---
+---@return uv.uv_signal_t|nil signal
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_signal() end
+
+
+--- Initialize the handle using a socket descriptor. On Unix this is identical to
+--- `uv.new_poll()`. On windows it takes a SOCKET handle.
+---
+--- The socket is set to non-blocking mode.
+---
+---@param  fd               integer
+---@return uv.uv_poll_t|nil poll
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_socket_poll(fd) end
+
+
+--- Creates and initializes a new `uv_tcp_t`. Returns the Lua userdata wrapping it.
+--- Flags may be a family string: `"unix"`, `"inet"`, `"inet6"`, `"ipx"`,
+--- `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, or `"packet"`.
+---
+---@param  flags?          uv.socket.family
+---@return uv.uv_tcp_t|nil tcp
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_tcp(flags) end
+
+
+
+--- Creates and initializes a `luv_thread_t` (not `uv_thread_t`). Returns the Lua
+--- userdata wrapping it and asynchronously executes `entry`, which can be either
+--- a Lua function or a Lua function dumped to a string. Additional arguments `...`
+--- are passed to the `entry` function and an optional `options` table may be
+--- provided. Currently accepted `option` fields are `stack_size`.
+---
+---@param  options?            uv.new_thread.options
+---@param  entry               function
+---@param  ...                 uv.threadargs
+---@return uv.luv_thread_t|nil thread
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_thread(options, entry, ...) end
+
+
+
+--- Creates and initializes a new `uv_timer_t`. Returns the Lua userdata wrapping
+--- it.
+---
+--- ```lua
+--- -- Creating a simple setTimeout wrapper
+--- local function setTimeout(timeout, callback)
+---   local timer = uv.new_timer()
+---   timer:start(timeout, 0, function ()
+---     timer:stop()
+---     timer:close()
+---     callback()
+---   end)
+---   return timer
+--- end
+---
+--- -- Creating a simple setInterval wrapper
+--- local function setInterval(interval, callback)
+---   local timer = uv.new_timer()
+---   timer:start(interval, interval, function ()
+---     callback()
+---   end)
+---   return timer
+--- end
+---
+--- -- And clearInterval
+--- local function clearInterval(timer)
+---   timer:stop()
+---   timer:close()
+--- end
+--- ```
+---
+---@return uv.uv_timer_t|nil timer
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_timer() end
+
+
+
+--- Initialize a new TTY stream with the given file descriptor. Usually the file
+--- descriptor will be:
+---
+---  - 0 - stdin
+---  - 1 - stdout
+---  - 2 - stderr
+---
+--- On Unix this function will determine the path of the fd of the terminal using
+--- ttyname_r(3), open it, and use it if the passed file descriptor refers to a TTY.
+--- This lets libuv put the tty in non-blocking mode without affecting other
+--- processes that share the tty.
+---
+--- This function is not thread safe on systems that don’t support ioctl TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and Solaris.
+---
+--- **Note:** If reopening the TTY fails, libuv falls back to blocking writes.
+---
+---@param  fd              integer
+---@param  readable        boolean
+---@return uv.uv_tty_t|nil tty
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_tty(fd, readable) end
+
+
+
+--- Creates and initializes a new `uv_udp_t`. Returns the Lua userdata wrapping
+--- it. The actual socket is created lazily.
+---
+--- When specified, `family` must be one of `"unix"`, `"inet"`, `"inet6"`,
+--- `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, or
+--- `"packet"`.
+---
+--- When specified, `mmsgs` determines the number of messages able to be received
+--- at one time via `recvmmsg(2)` (the allocated buffer will be sized to be able
+--- to fit the specified number of max size dgrams). Only has an effect on
+--- platforms that support `recvmmsg(2)`.
+---
+--- **Note:** For backwards compatibility reasons, `flags` can also be a string or
+--- integer. When it is a string, it will be treated like the `family` key above.
+--- When it is an integer, it will be used directly as the `flags` parameter when
+--- calling `uv_udp_init_ex`.
+---
+--- **Returns:** `uv_udp_t userdata` or `fail`
+---
+---@param  flags?          uv.new_udp.flags|uv.new_udp.flags.family|integer
+---@return uv.uv_udp_t|nil udp
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.new_udp(flags) end
+
+
+
+--- Creates and initializes a new `luv_work_ctx_t` (not `uv_work_t`). Returns the
+--- Lua userdata wrapping it.
+---
+--- **Parameters:**
+--- - `work_callback`: `function`
+---   - `...`: `threadargs` passed to/from `uv.queue_work(work_ctx, ...)`
+--- - `after_work_callback`: `function`
+---   - `...`: `threadargs` returned from `work_callback`
+---
+---@param work_callback uv.new_work.work_callback
+---@param after_work_callback uv.new_work.after_work_callback
+---@return uv.luv_work_ctx_t
+function uv.new_work(work_callback, after_work_callback) end
+
+
+
+--- Returns the current timestamp in milliseconds. The timestamp is cached at the
+--- start of the event loop tick, see `uv.update_time()` for details and rationale.
+---
+--- The timestamp increases monotonically from some arbitrary point in time. Don't
+--- make assumptions about the starting point, you will only get disappointed.
+---
+--- **Note**: Use `uv.hrtime()` if you need sub-millisecond granularity.
+---
+---@return integer
+function uv.now() end
+
+
+
+--- Returns all environmental variables as a dynamic table of names associated with
+--- their corresponding values.
+---
+--- **Warning:** This function is not thread safe.
+---
+---@return table<string, string>
+function uv.os_environ() end
+
+
+
+--- Returns password file information.
+---
+---@return uv.os_get_passwd.info
+function uv.os_get_passwd() end
+
+
+
+--- Returns the environment variable specified by `name` as string. The internal
+--- buffer size can be set by defining `size`. If omitted, `LUAL_BUFFERSIZE` is
+--- used. If the environment variable exceeds the storage available in the internal
+--- buffer, `ENOBUFS` is returned. If no matching environment variable exists,
+--- `ENOENT` is returned.
+---
+--- **Warning:** This function is not thread safe.
+---
+---@param name string
+---@param size? integer # (default = `LUAL_BUFFERSIZE`)
+---@return string|nil value
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_getenv(name, size) end
+
+
+
+--- Returns the hostname.
+---
+---@return string
+function uv.os_gethostname() end
+
+
+
+--- Returns the current process ID.
+---
+---@return number
+function uv.os_getpid() end
+
+
+
+--- Returns the parent process ID.
+---
+---@return number
+function uv.os_getppid() end
+
+
+
+--- Returns the scheduling priority of the process specified by `pid`.
+---
+---@param  pid        integer
+---@return number|nil priority
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_getpriority(pid) end
+
+
+
+--- **Warning:** This function is not thread safe.
+---
+---@return string|nil homedir
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_homedir() end
+
+
+--- Sets the environmental variable specified by `name` with the string `value`.
+---
+--- **Warning:** This function is not thread safe.
+---
+---@param  name        string
+---@param  value       string
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_setenv(name, value) end
+
+
+--- Sets the scheduling priority of the process specified by `pid`. The `priority`
+--- range is between -20 (high priority) and 19 (low priority).
+---
+---@param  pid         integer
+---@param  priority    integer
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_setpriority(pid, priority) end
+
+
+--- **Warning:** This function is not thread safe.
+---
+---@return string|nil tmpdir
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_tmpdir() end
+
+
+
+--- Returns system information.
+---
+---@return uv.os_uname.info
+function uv.os_uname() end
+
+
+
+--- **Warning:** This function is not thread safe.
+---
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.os_unsetenv() end
+
+
+
+--- Create a pair of connected pipe handles. Data may be written to the `write` fd and read from the `read` fd. The resulting handles can be passed to `pipe_open`, used with `spawn`, or for any other purpose.
+---
+--- Flags:
+---  - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise.
+---
+--- Equivalent to `pipe(2)` with the `O_CLOEXEC` flag set.
+---
+--- **Returns:** `table` or `fail`
+--- - `read` : `integer` (file descriptor)
+--- - `write` : `integer` (file descriptor)
+---
+--- ```lua
+--- -- Simple read/write with pipe_open
+--- local fds = uv.pipe({nonblock=true}, {nonblock=true})
+---
+--- local read_pipe = uv.new_pipe()
+--- read_pipe:open(fds.read)
+---
+--- local write_pipe = uv.new_pipe()
+--- write_pipe:open(fds.write)
+---
+--- write_pipe:write("hello")
+--- read_pipe:read_start(function(err, chunk)
+---   assert(not err, err)
+---   print(chunk)
+--- end)
+--- ```
+---
+---@param  read_flags      uv.pipe.read_flags
+---@param  write_flags     uv.pipe.write_flags
+---@return uv.pipe.fds|nil fds
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe(read_flags, write_flags) end
+
+
+--- Bind the pipe to a file path (Unix) or a name (Windows).
+---
+--- **Note**: Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes,
+--- typically between 92 and 108 bytes.
+---
+---@param  pipe       uv.uv_pipe_t
+---@param  name       string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_bind(pipe, name) end
+
+
+--- Alters pipe permissions, allowing it to be accessed from processes run by different users.
+--- Makes the pipe writable or readable by all users. `flags` are: `"r"`, `"w"`, `"rw"`, or `"wr"`
+--- where `r` is `READABLE` and `w` is `WRITABLE`. This function is blocking.
+---
+---@param  pipe       uv.uv_pipe_t
+---@param  flags      uv.pipe_chmod.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_chmod(pipe, flags) end
+
+
+--- Connect to the Unix domain socket or the named pipe.
+---
+--- **Note**: Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes,
+--- typically between 92 and 108 bytes.
+---
+---@param  pipe                uv.uv_pipe_t
+---@param  name                string
+---@param  callback?           uv.pipe_connect.callback
+---@return uv.uv_connect_t|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_connect(pipe, name, callback) end
+
+
+--- Get the name of the Unix domain socket or the named pipe to which the handle is
+--- connected.
+---
+---@param  pipe       uv.uv_pipe_t
+---@return string|nil peername
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_getpeername(pipe) end
+
+
+--- Get the name of the Unix domain socket or the named pipe.
+---
+---@param  pipe       uv.uv_pipe_t
+---@return string|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_getsockname(pipe) end
+
+
+--- Open an existing file descriptor or [`uv_handle_t`][] as a pipe.
+---
+--- **Note**: The file descriptor is set to non-blocking mode.
+---
+---@param  pipe       uv.uv_pipe_t
+---@param  fd         integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.pipe_open(pipe, fd) end
+
+
+--- Returns the pending pipe count for the named pipe.
+---
+---@param pipe uv.uv_pipe_t
+---@return integer
+function uv.pipe_pending_count(pipe) end
+
+
+--- Set the number of pending pipe instance handles when the pipe server is waiting
+--- for connections.
+---
+--- **Note**: This setting applies to Windows only.
+---
+---@param pipe uv.uv_pipe_t
+---@param count integer
+function uv.pipe_pending_instances(pipe, count) end
+
+
+--- Used to receive handles over IPC pipes.
+---
+--- First - call `uv.pipe_pending_count()`, if it's > 0 then initialize a handle of
+--- the given type, returned by `uv.pipe_pending_type()` and call
+--- `uv.accept(pipe, handle)`.
+---
+---@param pipe uv.uv_pipe_t
+---@return string
+function uv.pipe_pending_type(pipe) end
+
+
+--- Starts polling the file descriptor.
+---
+--- `events` are: `"r"`, `"w"`, `"rw"`, `"d"`,
+--- `"rd"`, `"wd"`, `"rwd"`, `"p"`, `"rp"`, `"wp"`, `"rwp"`, `"dp"`, `"rdp"`,
+--- `"wdp"`, or `"rwdp"` where `r` is `READABLE`, `w` is `WRITABLE`, `d` is
+--- `DISCONNECT`, and `p` is `PRIORITIZED`. As soon as an event is detected
+--- the callback will be called with status set to 0, and the detected events set on
+--- the events field.
+---
+--- The user should not close the socket while the handle is active. If the user
+--- does that anyway, the callback may be called reporting an error status, but this
+--- is not guaranteed.
+---
+--- **Note** Calling `uv.poll_start()` on a handle that is already active is fine.
+--- Doing so will update the events mask that is being watched for.
+---
+---@param  poll       uv.uv_poll_t
+---@param  events     uv.poll.eventspec
+---@param  callback   uv.poll_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.poll_start(poll, events, callback) end
+
+
+--- Stop polling the file descriptor, the callback will no longer be called.
+---
+---@param  poll       uv.uv_poll_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.poll_stop(poll) end
+
+
+--- Start the handle with the given callback.
+---
+---@param  prepare    uv.uv_prepare_t
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.prepare_start(prepare, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param  prepare    uv.uv_prepare_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.prepare_stop(prepare) end
+
+
+--- The same as `uv.print_all_handles()` except only active handles are printed.
+---
+--- **Note:** This is not available on Windows.
+---
+--- **Warning:** This function is meant for ad hoc debugging, there are no API/ABI
+--- stability guarantees.
+function uv.print_active_handles() end
+
+
+--- Prints all handles associated with the main loop to stderr. The format is
+--- `[flags] handle-type handle-address`. Flags are `R` for referenced, `A` for
+--- active and `I` for internal.
+---
+--- **Note:** This is not available on Windows.
+---
+--- **Warning:** This function is meant for ad hoc debugging, there are no API/ABI
+--- stability guarantees.
+function uv.print_all_handles() end
+
+
+--- Returns the handle's pid.
+---
+---@param process uv.uv_process_t
+---@return integer pid
+function uv.process_get_pid(process) end
+
+
+--- Sends the specified signal to the given process handle. Check the documentation
+--- on `uv_signal_t` for signal support, specially on Windows.
+---
+---@param  process    uv.uv_process_t
+---@param  signum     integer|string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.process_kill(process, signum) end
+
+
+--- Queues a work request which will run `work_callback` in a new Lua state in a
+--- thread from the threadpool with any additional arguments from `...`. Values
+--- returned from `work_callback` are passed to `after_work_callback`, which is
+--- called in the main loop thread.
+---
+---@param  work_ctx    uv.luv_work_ctx_t
+---@param  ...         uv.threadargs
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.queue_work(work_ctx, ...) end
+
+
+--- Fills a string of length `len` with cryptographically strong random bytes
+--- acquired from the system CSPRNG. `flags` is reserved for future extension
+--- and must currently be `nil` or `0` or `{}`.
+---
+--- Short reads are not possible. When less than `len` random bytes are available,
+--- a non-zero error value is returned or passed to the callback. If the callback
+--- is omitted, this function is completed synchronously.
+---
+--- The synchronous version may block indefinitely when not enough entropy is
+--- available. The asynchronous version may not ever finish when the system is
+--- low on entropy.
+---
+--- **Returns (sync version):** `string` or `fail`
+---
+--- **Returns (async version):** `0` or `fail`
+---
+---@param  len        integer
+---@param  flags?     nil|0|{}
+---@return string|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(len:integer, flags?:nil, callback:uv.random.callback):0|nil, string?, string?
+function uv.random(len, flags) end
+
+
+--- Read data from an incoming stream. The callback will be made several times until
+--- there is no more data to read or `uv.read_stop()` is called. When we've reached
+--- EOF, `data` will be `nil`.
+---
+--- ```lua
+--- stream:read_start(function (err, chunk)
+---   if err then
+---     -- handle read error
+---   elseif chunk then
+---     -- handle data
+---   else
+---     -- handle disconnect
+---   end
+--- end)
+--- ```
+---
+---@param  stream     uv.uv_stream_t
+---@param  callback   uv.read_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.read_start(stream, callback) end
+
+
+--- Stop reading data from the stream. The read callback will no longer be called.
+---
+--- This function is idempotent and may be safely called on a stopped stream.
+---
+---@param  stream     uv.uv_stream_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.read_stop(stream) end
+
+
+--- Gets or sets the size of the receive buffer that the operating system uses for
+--- the socket.
+---
+--- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size.
+---
+--- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP
+--- handles on Windows.
+---
+--- **Note**: Linux will set double the size and return double the size of the
+--- original set value.
+---
+---@param  handle      uv.uv_handle_t
+---@param  size?       integer  # default is `0`
+---@return integer|nil size_or_ok
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.recv_buffer_size(handle, size) end
+
+
+--- Reference the given handle. References are idempotent, that is, if a handle is
+--- already referenced calling this function again will have no effect.
+---
+--- See [Reference counting][].
+---
+---@param handle uv.uv_handle_t
+function uv.ref(handle) end
+
+
+--- Returns the name of the struct for a given request (e.g. `"fs"` for `uv_fs_t`)
+--- and the libuv enum integer for the request's type (`uv_req_type`).
+---
+---@param  req              uv.uv_req_t
+---@return uv.req_type.name type
+---@return uv.req_type.enum enum
+function uv.req_get_type(req) end
+
+
+--- Returns the resident set size (RSS) for the current process.
+---
+---@return integer|nil rss
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.resident_set_memory() end
+
+
+--- This function runs the event loop. It will act differently depending on the
+--- specified mode:
+---
+---   - `"default"`: Runs the event loop until there are no more active and
+---   referenced handles or requests. Returns `true` if `uv.stop()` was called and
+---   there are still active handles or requests. Returns `false` in all other
+---   cases.
+---
+---   - `"once"`: Poll for I/O once. Note that this function blocks if there are no
+---   pending callbacks. Returns `false` when done (no active handles or requests
+---   left), or `true` if more callbacks are expected (meaning you should run the
+---   event loop again sometime in the future).
+---
+---   - `"nowait"`: Poll for I/O once but don't block if there are no pending
+---   callbacks. Returns `false` if done (no active handles or requests left),
+---   or `true` if more callbacks are expected (meaning you should run the event
+---   loop again sometime in the future).
+---
+--- **Note:** Luvit will implicitly call `uv.run()` after loading user code, but if
+--- you use the luv bindings directly, you need to call this after registering
+--- your initial set of event callbacks to start the event loop.
+---
+---@param mode? uv.run.mode
+---@return boolean
+function uv.run(mode) end
+
+
+--- Gets or sets the size of the send buffer that the operating system uses for the
+--- socket.
+---
+--- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size.
+---
+--- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP
+--- handles on Windows.
+---
+--- **Returns:**
+--- - `integer` or `fail` (if `size` is `nil` or `0`)
+--- - `0` or `fail` (if `size` is not `nil` and not `0`)
+---
+--- **Note**: Linux will set double the size and return double the size of the
+--- original set value.
+---
+---@param  handle               uv.uv_handle_t
+---@param  size?                integer|0
+---@return integer|nil          success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil    err_name
+---
+---@overload fun(handle: uv.uv_handle_t):(size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+---@overload fun(handle: uv.uv_handle_t, size:0):(size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+function uv.send_buffer_size(handle, size) end
+
+
+--- Sets the title of the current process with the string `title`.
+---
+---@param  title      string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.set_process_title(title) end
+
+
+--- Sets the group ID of the process with the integer `id`.
+---
+--- **Note:** This is not a libuv function and is not supported on Windows.
+---
+---@param id integer
+function uv.setgid(id) end
+
+
+--- Sets the user ID of the process with the integer `id`.
+---
+--- **Note:** This is not a libuv function and is not supported on Windows.
+---
+---@param id integer
+function uv.setuid(id) end
+
+
+--- Shutdown the outgoing (write) side of a duplex stream. It waits for pending
+--- write requests to complete. The callback is called after shutdown is complete.
+---
+---@param  stream               uv.uv_stream_t
+---@param  callback?            uv.shutdown.callback
+---@return uv.uv_shutdown_t|nil shutdown
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.shutdown(stream, callback) end
+
+
+--- Start the handle with the given callback, watching for the given signal.
+---
+---@param  signal     uv.uv_signal_t
+---@param  signum     integer|string
+---@param  callback   uv.signal_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.signal_start(signal, signum, callback) end
+
+
+--- Same functionality as `uv.signal_start()` but the signal handler is reset the moment the signal is received.
+---
+---@param  signal     uv.uv_signal_t
+---@param  signum     integer|string
+---@param  callback   uv.signal_start_oneshot.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.signal_start_oneshot(signal, signum, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---@param  signal     uv.uv_signal_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.signal_stop(signal) end
+
+
+--- Pauses the thread in which this is called for a number of milliseconds.
+---@param msec integer
+function uv.sleep(msec) end
+
+
+--- Create a pair of connected sockets with the specified properties. The resulting handles can be passed to `uv.tcp_open`, used with `uv.spawn`, or for any other purpose.
+---
+--- When specified as a string, `socktype` must be one of `"stream"`, `"dgram"`, `"raw"`,
+--- `"rdm"`, or `"seqpacket"`.
+---
+--- When `protocol` is set to 0 or nil, it will be automatically chosen based on the socket's domain and type. When `protocol` is specified as a string, it will be looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc).
+---
+--- Flags:
+---  - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise.
+---
+--- Equivalent to `socketpair(2)` with a domain of `AF_UNIX`.
+---
+--- **Returns:** `table` or `fail`
+--- - `[1, 2]` : `integer` (file descriptor)
+---
+--- ```lua
+--- -- Simple read/write with tcp
+--- local fds = uv.socketpair(nil, nil, {nonblock=true}, {nonblock=true})
+---
+--- local sock1 = uv.new_tcp()
+--- sock1:open(fds[1])
+---
+--- local sock2 = uv.new_tcp()
+--- sock2:open(fds[2])
+---
+--- sock1:write("hello")
+--- sock2:read_start(function(err, chunk)
+---   assert(not err, err)
+---   print(chunk)
+--- end)
+--- ```
+---
+---@param  socktype?  uv.socketpair.socktype
+---@param  protocol?  uv.socketpair.protocol
+---@param  flags1?    uv.socketpair.flags
+---@param  flags2?    uv.socketpair.flags
+---@return uv.socketpair.fds|nil fds
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.socketpair(socktype, protocol, flags1, flags2) end
+
+
+--- Initializes the process handle and starts the process. If the process is
+--- successfully spawned, this function will return the handle and pid of the child
+--- process.
+---
+--- Possible reasons for failing to spawn would include (but not be limited to) the
+--- file to execute not existing, not having permissions to use the setuid or setgid
+--- specified, or not having enough memory to allocate for the new process.
+---
+--- ```lua
+--- local stdin = uv.new_pipe()
+--- local stdout = uv.new_pipe()
+--- local stderr = uv.new_pipe()
+---
+--- print("stdin", stdin)
+--- print("stdout", stdout)
+--- print("stderr", stderr)
+---
+--- local handle, pid = uv.spawn("cat", {
+---   stdio = {stdin, stdout, stderr}
+--- }, function(code, signal) -- on exit
+---   print("exit code", code)
+---   print("exit signal", signal)
+--- end)
+---
+--- print("process opened", handle, pid)
+---
+--- uv.read_start(stdout, function(err, data)
+---   assert(not err, err)
+---   if data then
+---     print("stdout chunk", stdout, data)
+---   else
+---     print("stdout end", stdout)
+---   end
+--- end)
+---
+--- uv.read_start(stderr, function(err, data)
+---   assert(not err, err)
+---   if data then
+---     print("stderr chunk", stderr, data)
+---   else
+---     print("stderr end", stderr)
+---   end
+--- end)
+---
+--- uv.write(stdin, "Hello World")
+---
+--- uv.shutdown(stdin, function()
+---   print("stdin shutdown", stdin)
+---   uv.close(handle, function()
+---     print("process closed", handle, pid)
+---   end)
+--- end)
+--- ```
+---
+--- The `options` table accepts the following fields:
+---
+---   - `options.args` - Command line arguments as a list of string. The first
+---   string should be the path to the program. On Windows, this uses CreateProcess
+---   which concatenates the arguments into a string. This can cause some strange
+---   errors. (See `options.verbatim` below for Windows.)
+---   - `options.stdio` - Set the file descriptors that will be made available to
+---   the child process. The convention is that the first entries are stdin, stdout,
+---   and stderr. (**Note**: On Windows, file descriptors after the third are
+---   available to the child process only if the child processes uses the MSVCRT
+---   runtime.)
+---   - `options.env` - Set environment variables for the new process.
+---   - `options.cwd` - Set the current working directory for the sub-process.
+---   - `options.uid` - Set the child process' user id.
+---   - `options.gid` - Set the child process' group id.
+---   - `options.verbatim` - If true, do not wrap any arguments in quotes, or
+---   perform any other escaping, when converting the argument list into a command
+---   line string. This option is only meaningful on Windows systems. On Unix it is
+---   silently ignored.
+---   - `options.detached` - If true, spawn the child process in a detached state -
+---   this will make it a process group leader, and will effectively enable the
+---   child to keep running after the parent exits. Note that the child process
+---   will still keep the parent's event loop alive unless the parent process calls
+---   `uv.unref()` on the child's process handle.
+---   - `options.hide` - If true, hide the subprocess console window that would
+---   normally be created. This option is only meaningful on Windows systems. On
+---   Unix it is silently ignored.
+---
+--- The `options.stdio` entries can take many shapes.
+---
+---   - If they are numbers, then the child process inherits that same zero-indexed
+---   fd from the parent process.
+---   - If `uv_stream_t` handles are passed in, those are used as a read-write pipe
+---   or inherited stream depending if the stream has a valid fd.
+---   - Including `nil` placeholders means to ignore that fd in the child process.
+---
+--- When the child process exits, `on_exit` is called with an exit code and signal.
+---
+---@param  path            string
+---@param  options         uv.spawn.options
+---@param  on_exit         uv.spawn.on_exit
+---@return uv.uv_process_t proc
+---@return integer         pid
+function uv.spawn(path, options, on_exit) end
+
+
+--- Stop the event loop, causing `uv.run()` to end as soon as possible. This
+--- will happen not sooner than the next loop iteration. If this function was called
+--- before blocking for I/O, the loop won't block for I/O on this iteration.
+function uv.stop() end
+
+
+--- Returns the stream's write queue size.
+---
+---@param stream uv.uv_stream_t
+---@return integer
+function uv.stream_get_write_queue_size(stream) end
+
+
+--- Enable or disable blocking mode for a stream.
+---
+--- When blocking mode is enabled all writes complete synchronously. The interface
+--- remains unchanged otherwise, e.g. completion or failure of the operation will
+--- still be reported through a callback which is made asynchronously.
+---
+--- **Warning**: Relying too much on this API is not recommended. It is likely to
+--- change significantly in the future. Currently this only works on Windows and
+--- only for `uv_pipe_t` handles. Also libuv currently makes no ordering guarantee
+--- when the blocking mode is changed after write requests have already been
+--- submitted. Therefore it is recommended to set the blocking mode immediately
+--- after opening or creating the stream.
+---
+---@param  stream     uv.uv_stream_t
+---@param  blocking   boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.stream_set_blocking(stream, blocking) end
+
+
+--- Bind the handle to an host and port. `host` should be an IP address and
+--- not a domain name. Any `flags` are set with a table with field `ipv6only`
+--- equal to `true` or `false`.
+---
+--- When the port is already taken, you can expect to see an `EADDRINUSE` error
+--- from either `uv.tcp_bind()`, `uv.listen()` or `uv.tcp_connect()`. That is, a
+--- successful call to this function does not guarantee that the call to `uv.listen()`
+--- or `uv.tcp_connect()` will succeed as well.
+---
+--- Use a port of `0` to let the OS assign an ephemeral port.  You can look it up
+--- later using `uv.tcp_getsockname()`.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  host       string
+---@param  port       integer
+---@param  flags?     uv.tcp_bind.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_bind(tcp, host, port, flags) end
+
+
+--- Resets a TCP connection by sending a RST packet. This is accomplished by setting
+--- the SO_LINGER socket option with a linger interval of zero and then calling
+--- `uv.close()`. Due to some platform inconsistencies, mixing of `uv.shutdown()`
+--- and `uv.tcp_close_reset()` calls is not allowed.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  callback?  function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_close_reset(tcp, callback) end
+
+
+--- Establish an IPv4 or IPv6 TCP connection.
+---
+--- ```lua
+--- local client = uv.new_tcp()
+--- client:connect("127.0.0.1", 8080, function (err)
+---   -- check error and carry on.
+--- end)
+--- ```
+---
+---@param  tcp                 uv.uv_tcp_t
+---@param  host                string
+---@param  port                integer
+---@param  callback            uv.tcp_connect.callback
+---@return uv.uv_connect_t|nil conn
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_connect(tcp, host, port, callback) end
+
+
+--- Get the address of the peer connected to the handle.
+---
+---@param  tcp               uv.uv_tcp_t
+---@return uv.socketinfo|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_getpeername(tcp) end
+
+
+--- Get the current address to which the handle is bound.
+---
+---@param  tcp               uv.uv_tcp_t
+---@return uv.socketinfo|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_getsockname(tcp) end
+
+
+--- Enable / disable TCP keep-alive. `delay` is the initial delay in seconds,
+--- ignored when enable is `false`.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  enable     boolean
+---@param  delay?     integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_keepalive(tcp, enable, delay) end
+
+
+--- Enable / disable Nagle's algorithm.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  enable     boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_nodelay(tcp, enable) end
+
+
+--- Open an existing file descriptor or SOCKET as a TCP handle.
+---
+--- **Note:** The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid stream socket.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  sock       integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_open(tcp, sock) end
+
+
+--- Enable / disable simultaneous asynchronous accept requests that are queued by
+--- the operating system when listening for new TCP connections.
+---
+--- This setting is used to tune a TCP server for the desired performance. Having
+--- simultaneous accepts can significantly improve the rate of accepting connections
+--- (which is why it is enabled by default) but may lead to uneven load distribution
+--- in multi-process setups.
+---
+---@param  tcp        uv.uv_tcp_t
+---@param  enable     boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tcp_simultaneous_accepts(tcp, enable) end
+
+
+--- **Deprecated:** Please use `uv.stream_get_write_queue_size()` instead.
+---
+---@param tcp uv.uv_tcp_t
+function uv.tcp_write_queue_size(tcp) end
+
+
+--- Returns a boolean indicating whether two threads are the same. This function is
+--- equivalent to the `__eq` metamethod.
+---
+---@param  thread       uv.luv_thread_t
+---@param  other_thread uv.luv_thread_t
+---@return boolean      equal
+function uv.thread_equal(thread, other_thread) end
+
+
+--- Waits for the `thread` to finish executing its entry function.
+---
+---@param  thread      uv.luv_thread_t
+---@return boolean|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.thread_join(thread) end
+
+
+--- Returns the handle for the thread in which this is called.
+---
+---@return uv.luv_thread_t
+function uv.thread_self() end
+
+
+--- Stop the timer, and if it is repeating restart it using the repeat value as the
+--- timeout. If the timer has never been started before it raises `EINVAL`.
+---
+---@param  timer      uv.uv_timer_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.timer_again(timer) end
+
+
+--- Get the timer due value or 0 if it has expired. The time is relative to `uv.now()`.
+---
+--- **Note**: New in libuv version 1.40.0.
+---
+---@param timer uv.uv_timer_t
+---@return integer
+function uv.timer_get_due_in(timer) end
+
+
+--- Get the timer repeat value.
+---
+---@param timer uv.uv_timer_t
+---@return integer
+function uv.timer_get_repeat(timer) end
+
+
+--- Set the repeat interval value in milliseconds. The timer will be scheduled to
+--- run on the given interval, regardless of the callback execution duration, and
+--- will follow normal timer semantics in the case of a time-slice overrun.
+---
+--- For example, if a 50 ms repeating timer first runs for 17 ms, it will be
+--- scheduled to run again 33 ms later. If other tasks consume more than the 33 ms
+--- following the first timer callback, then the callback will run as soon as
+--- possible.
+---
+---@param timer uv.uv_timer_t
+---@param repeat_ integer
+function uv.timer_set_repeat(timer, repeat_) end
+
+
+--- Start the timer. `timeout` and `repeat` are in milliseconds.
+---
+--- If `timeout` is zero, the callback fires on the next event loop iteration. If
+--- `repeat` is non-zero, the callback fires first after `timeout` milliseconds and
+--- then repeatedly after `repeat` milliseconds.
+---
+---@param  timer      uv.uv_timer_t
+---@param  timeout    integer
+---@param  repeat_    integer
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.timer_start(timer, timeout, repeat_, callback) end
+
+
+--- Stop the timer, the callback will not be called anymore.
+---
+---@param  timer      uv.uv_timer_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.timer_stop(timer) end
+
+
+--- Returns the libuv error message and error name (both in string form, see [`err` and `name` in Error Handling](#error-handling)) equivalent to the given platform dependent error code: POSIX error codes on Unix (the ones stored in errno), and Win32 error codes on Windows (those returned by GetLastError() or WSAGetLastError()).
+---
+---@param errcode integer
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.translate_sys_error(errcode) end
+
+
+--- Same as `uv.write()`, but won't queue a write request if it can't be completed
+--- immediately.
+---
+--- Will return number of bytes written (can be less than the supplied buffer size).
+---
+---@param  stream      uv.uv_stream_t
+---@param  data        uv.buffer
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.try_write(stream, data) end
+
+
+--- Like `uv.write2()`, but with the properties of `uv.try_write()`. Not supported on Windows, where it returns `UV_EAGAIN`.
+---
+--- Will return number of bytes written (can be less than the supplied buffer size).
+---
+--- **Returns:** `integer` or `fail`
+---
+---@param  stream      uv.uv_stream_t
+---@param  data        uv.buffer
+---@param  send_handle uv.uv_stream_t
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.try_write2(stream, data, send_handle) end
+
+
+--- Get the current state of whether console virtual terminal sequences are handled
+--- by libuv or the console. The return value is `"supported"` or `"unsupported"`.
+---
+--- This function is not implemented on Unix, where it returns `ENOTSUP`.
+---
+---@return "supported"|"unsupported"|nil state
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tty_get_vterm_state() end
+
+
+--- Gets the current Window width and height.
+---
+--- **Returns:** `integer, integer` or `fail`
+---
+---@param  tty            uv.uv_tty_t
+---@return integer|nil    width
+---@return integer|string height_or_errmsg
+---@return uv.error.name|nil err_name
+function uv.tty_get_winsize(tty) end
+
+
+--- To be called when the program exits. Resets TTY settings to default values for
+--- the next process to take over.
+---
+--- This function is async signal-safe on Unix platforms but can fail with error
+--- code `EBUSY` if you call it when execution is inside `uv.tty_set_mode()`.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tty_reset_mode() end
+
+
+--- Set the TTY using the specified terminal mode.
+---
+--- Parameter `mode` is a C enum with the following values:
+---
+---   - 0 - UV_TTY_MODE_NORMAL: Initial/normal terminal mode
+---   - 1 - UV_TTY_MODE_RAW: Raw input mode (On Windows, ENABLE_WINDOW_INPUT is
+---   also enabled)
+---   - 2 - UV_TTY_MODE_IO: Binary-safe I/O mode for IPC (Unix-only)
+---
+---@param  tty        uv.uv_tty_t
+---@param  mode       uv.tty.mode
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.tty_set_mode(tty, mode) end
+
+
+--- Controls whether console virtual terminal sequences are processed by libuv or
+--- console. Useful in particular for enabling ConEmu support of ANSI X3.64 and
+--- Xterm 256 colors. Otherwise Windows10 consoles are usually detected
+--- automatically. State should be one of: `"supported"` or `"unsupported"`.
+---
+--- This function is only meaningful on Windows systems. On Unix it is silently
+--- ignored.
+---
+---@param state "supported"|"unsupported"
+function uv.tty_set_vterm_state(state) end
+
+
+--- Bind the UDP handle to an IP address and port. Any `flags` are set with a table
+--- with fields `reuseaddr` or `ipv6only` equal to `true` or `false`.
+---
+---@param  udp        uv.uv_udp_t
+---@param  host       string
+---@param  port       integer
+---@param  flags?     uv.udp_bind.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_bind(udp, host, port, flags) end
+
+
+--- Associate the UDP handle to a remote address and port, so every message sent by
+--- this handle is automatically sent to that destination. Calling this function
+--- with a NULL addr disconnects the handle. Trying to call `uv.udp_connect()` on an
+--- already connected handle will result in an `EISCONN` error. Trying to disconnect
+--- a handle that is not connected will return an `ENOTCONN` error.
+---
+---@param udp uv.uv_udp_t
+---@param host string
+---@param port integer
+---@return 0|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_connect(udp, host, port) end
+
+
+--- Returns the handle's send queue count.
+---
+---@param  udp     uv.uv_udp_t
+---@return integer count
+function uv.udp_get_send_queue_count(udp) end
+
+
+--- Returns the handle's send queue size.
+---
+---@param  udp     uv.uv_udp_t
+---@return integer size
+function uv.udp_get_send_queue_size(udp) end
+
+
+--- Get the remote IP and port of the UDP handle on connected UDP handles.
+---
+---@param  udp                 uv.uv_udp_t
+---@return uv.udp.sockname|nil peername
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_getpeername(udp) end
+
+
+--- Get the local IP and port of the UDP handle.
+---
+---@param  udp                 uv.uv_udp_t
+---@return uv.udp.sockname|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_getsockname(udp) end
+
+
+--- Opens an existing file descriptor or Windows SOCKET as a UDP handle.
+---
+--- Unix only: The only requirement of the sock argument is that it follows the
+--- datagram contract (works in unconnected mode, supports sendmsg()/recvmsg(),
+--- etc). In other words, other datagram-type sockets like raw sockets or netlink
+--- sockets can also be passed to this function.
+---
+--- The file descriptor is set to non-blocking mode.
+---
+--- Note: The passed file descriptor or SOCKET is not checked for its type, but
+--- it's required that it represents a valid datagram socket.
+---
+---@param  udp        uv.uv_udp_t
+---@param  fd         integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_open(udp, fd) end
+
+
+--- Prepare for receiving data. If the socket has not previously been bound with
+--- `uv.udp_bind()` it is bound to `0.0.0.0` (the "all interfaces" IPv4 address)
+--- and a random port number.
+---
+---@param  udp        uv.uv_udp_t
+---@param  callback   uv.udp_recv_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_recv_start(udp, callback) end
+
+
+--- Stop listening for incoming datagrams.
+---
+---@param  udp        uv.uv_udp_t
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_recv_stop(udp) end
+
+
+--- Send data over the UDP socket. If the socket has not previously been bound
+--- with `uv.udp_bind()` it will be bound to `0.0.0.0` (the "all interfaces" IPv4
+--- address) and a random port number.
+---
+---@param  udp                  uv.uv_udp_t
+---@param  data                 uv.buffer
+---@param  host                 string
+---@param  port                 integer
+---@param  callback             uv.udp_send.callback
+---@return uv.uv_udp_send_t|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_send(udp, data, host, port, callback) end
+
+
+--- Set broadcast on or off.
+---
+---@param  udp        uv.uv_udp_t
+---@param  on         boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_broadcast(udp, on) end
+
+
+--- Set membership for a multicast address. `multicast_addr` is multicast address to
+--- set membership for. `interface_addr` is interface address. `membership` can be
+--- the string `"leave"` or `"join"`.
+---
+---@param  udp            uv.uv_udp_t
+---@param  multicast_addr string
+---@param  interface_addr string
+---@param  membership     "leave"|"join"
+---@return 0|nil          success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_membership(udp, multicast_addr, interface_addr, membership) end
+
+
+--- Set the multicast interface to send or receive data on.
+---
+---@param  udp            uv.uv_udp_t
+---@param  interface_addr string
+---@return 0|nil          success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_multicast_interface(udp, interface_addr) end
+
+
+--- Set IP multicast loop flag. Makes multicast packets loop back to local
+--- sockets.
+---
+---@param  udp        uv.uv_udp_t
+---@param  on         boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_multicast_loop(udp, on) end
+
+
+--- Set the multicast ttl.
+---
+--- `ttl` is an integer 1 through 255.
+---
+---@param  udp        uv.uv_udp_t
+---@param  ttl        integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_multicast_ttl(udp, ttl) end
+
+
+--- Set membership for a source-specific multicast group. `multicast_addr` is multicast
+--- address to set membership for. `interface_addr` is interface address. `source_addr`
+--- is source address. `membership` can be the string `"leave"` or `"join"`.
+---
+---@param  udp            uv.uv_udp_t
+---@param  multicast_addr string
+---@param  interface_addr string|nil
+---@param  source_addr    string
+---@param  membership     "leave"|"join"
+---@return 0|nil          success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_source_membership(udp, multicast_addr, interface_addr, source_addr, membership) end
+
+
+--- Set the time to live.
+---
+--- `ttl` is an integer 1 through 255.
+---
+---@param  udp        uv.uv_udp_t
+---@param  ttl        integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_set_ttl(udp, ttl) end
+
+
+--- Same as `uv.udp_send()`, but won't queue a send request if it can't be
+--- completed immediately.
+---
+---@param  udp         uv.uv_udp_t
+---@param  data        uv.buffer
+---@param  host        string
+---@param  port        integer
+---@return integer|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.udp_try_send(udp, data, host, port) end
+
+
+--- Un-reference the given handle. References are idempotent, that is, if a handle
+--- is not referenced calling this function again will have no effect.
+---
+--- See [Reference counting][].
+---
+---@param handle uv.uv_handle_t
+function uv.unref(handle) end
+
+
+--- Update the event loop's concept of "now". Libuv caches the current time at the
+--- start of the event loop tick in order to reduce the number of time-related
+--- system calls.
+---
+--- You won't normally need to call this function unless you have callbacks that
+--- block the event loop for longer periods of time, where "longer" is somewhat
+--- subjective but probably on the order of a millisecond or more.
+function uv.update_time() end
+
+
+--- Returns the current system uptime in seconds.
+---
+---@return number|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.uptime() end
+
+
+--- Returns the libuv version packed into a single integer. 8 bits are used for each
+--- component, with the patch number stored in the 8 least significant bits. For
+--- example, this would be 0x010203 in libuv 1.2.3.
+---
+---@return integer
+function uv.version() end
+
+
+--- Returns the libuv version number as a string. For example, this would be "1.2.3"
+--- in libuv 1.2.3. For non-release versions, the version suffix is included.
+---
+---@return string
+function uv.version_string() end
+
+
+--- Walk the list of handles: `callback` will be executed with each handle.
+---
+--- ```lua
+--- -- Example usage of uv.walk to close all handles that aren't already closing.
+--- uv.walk(function (handle)
+---   if not handle:is_closing() then
+---     handle:close()
+---   end
+--- end)
+--- ```
+---
+---@param callback uv.walk.callback
+function uv.walk(callback) end
+
+
+--- Write data to stream.
+---
+--- `data` can either be a Lua string or a table of strings. If a table is passed
+--- in, the C backend will use writev to send all strings in a single system call.
+---
+--- The optional `callback` is for knowing when the write is complete.
+---
+---@param  stream            uv.uv_stream_t
+---@param  data              uv.buffer
+---@param  callback?         uv.write.callback
+---@return uv.uv_write_t|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.write(stream, data, callback) end
+
+--- Extended write function for sending handles over a pipe. The pipe must be
+--- initialized with `ipc` option `true`.
+---
+--- **Note:** `send_handle` must be a TCP socket or pipe, which is a server or a
+--- connection (listening or connected state). Bound sockets or pipes will be
+--- assumed to be servers.
+---
+---@param  stream            uv.uv_stream_t
+---@param  data              uv.buffer
+---@param  send_handle       uv.uv_stream_t
+---@param  callback?         uv.write2.callback
+---@return uv.uv_write_t|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function uv.write2(stream, data, send_handle, callback) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_async_t.lua b/meta/3rd/luv/library/uv_async_t.lua
new file mode 100644
index 000000000..73f17e386
--- /dev/null
+++ b/meta/3rd/luv/library/uv_async_t.lua
@@ -0,0 +1,35 @@
+---@meta
+
+--- Async handles allow the user to "wakeup" the event loop and get a callback
+--- called from another thread.
+---
+--- ```lua
+--- local async
+--- async = uv.new_async(function()
+---   print("async operation ran")
+---   async:close()
+--- end)
+---
+--- async:send()
+--- ```
+---
+---@class uv.uv_async_t : uv.uv_handle_t
+---
+local async
+
+--- Wakeup the event loop and call the async handle's callback.
+---
+--- **Note**: It's safe to call this function from any thread. The callback will be
+--- called on the loop thread.
+---
+--- **Warning**: libuv will coalesce calls to `uv.async_send(async)`, that is, not
+--- every call to it will yield an execution of the callback. For example: if
+--- `uv.async_send()` is called 5 times in a row before the callback is called, the
+--- callback will only be called once. If `uv.async_send()` is called again after
+--- the callback was called, it will be called again.
+---
+---@param  ...        uv.threadargs
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function async:send(...) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_check_t.lua b/meta/3rd/luv/library/uv_check_t.lua
new file mode 100644
index 000000000..3c131ba9d
--- /dev/null
+++ b/meta/3rd/luv/library/uv_check_t.lua
@@ -0,0 +1,29 @@
+---@meta
+
+--- Check handles will run the given callback once per loop iteration, right after
+--- polling for I/O.
+---
+--- ```lua
+--- local check = uv.new_check()
+--- check:start(function()
+---   print("After I/O polling")
+--- end)
+--- ```
+---
+---@class uv.uv_check_t : uv.uv_handle_t
+local check
+
+--- Start the handle with the given callback.
+---
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function check:start(callback) end
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function check:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_fs_event_t.lua b/meta/3rd/luv/library/uv_fs_event_t.lua
new file mode 100644
index 000000000..87fab6b24
--- /dev/null
+++ b/meta/3rd/luv/library/uv_fs_event_t.lua
@@ -0,0 +1,33 @@
+---@meta
+
+--- FS Event handles allow the user to monitor a given path for changes, for
+--- example, if the file was renamed or there was a generic change in it. This
+--- handle uses the best backend for the job on each platform.
+---
+---@class uv.uv_fs_event_t : uv.uv_handle_t
+local fs_event
+
+--- Get the path being monitored by the handle.
+---
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_event:getpath() end
+
+--- Start the handle with the given callback, which will watch the specified path
+--- for changes.
+---
+---@param  path       string
+---@param  flags      uv.fs_event_start.flags
+---@param  callback   uv.fs_event_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_event:start(path, flags, callback) end
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_event:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_fs_poll_t.lua b/meta/3rd/luv/library/uv_fs_poll_t.lua
new file mode 100644
index 000000000..2e65cc42d
--- /dev/null
+++ b/meta/3rd/luv/library/uv_fs_poll_t.lua
@@ -0,0 +1,35 @@
+---@meta
+
+--- FS Poll handles allow the user to monitor a given path for changes. Unlike
+--- `uv_fs_event_t`, fs poll handles use `stat` to detect when a file has changed so
+--- they can work on file systems where fs event handles can't.
+---
+---@class uv.uv_fs_poll_t : uv.uv_handle_t
+local fs_poll
+
+--- Get the path being monitored by the handle.
+---
+---@return string|nil path
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_poll:getpath() end
+
+--- Check the file at `path` for changes every `interval` milliseconds.
+---
+--- **Note:** For maximum portability, use multi-second intervals. Sub-second
+--- intervals will not detect all changes on many file systems.
+---
+---@param  path       string
+---@param  interval   integer
+---@param  callback   uv.fs_poll_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_poll:start(path, interval, callback) end
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function fs_poll:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_handle_t.lua b/meta/3rd/luv/library/uv_handle_t.lua
new file mode 100644
index 000000000..b0d25bc10
--- /dev/null
+++ b/meta/3rd/luv/library/uv_handle_t.lua
@@ -0,0 +1,132 @@
+---@meta
+
+--- Base handle
+---
+--- `uv_handle_t` is the base type for all libuv handle types. All API functions
+--- defined here work with any handle type.
+---
+---@class uv.uv_handle_t : table
+---
+local handle
+
+--- Request handle to be closed.
+---
+--- The `callback` will be called asynchronously after this call.
+---
+--- This MUST be called on each handle before memory is released.
+---
+--- Handles that wrap file descriptors are closed immediately but `callback` will
+--- still be deferred to the next iteration of the event loop. It gives you a chance
+--- to free up any resources associated with the handle.
+---
+--- In-progress requests, like `uv_connect_t` or `uv_write_t`, are cancelled and
+--- have their callbacks called asynchronously with `ECANCELED`.
+---
+---@param callback? function
+function handle:close(callback) end
+
+--- Gets the platform dependent file descriptor equivalent.
+---
+--- The following handles are supported: TCP, pipes, TTY, UDP and poll. Calling
+--- this method on other handle type will fail with `EINVAL`.
+---
+--- If a handle doesn't have an attached file descriptor yet or the handle itself
+--- has been closed, this function will return `EBADF`.
+---
+--- **Warning**: Be very careful when using this function. libuv assumes it's in
+--- control of the file descriptor so any change to it may lead to malfunction.
+---
+---@return integer|nil fileno
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function handle:fileno() end
+
+--- Returns the name of the struct for a given handle (e.g. `"pipe"` for `uv_pipe_t`)
+--- and the libuv enum integer for the handle's type (`uv_handle_type`).
+---
+---@return string type
+---@return integer enum
+function handle:get_type() end
+
+--- Returns `true` if the handle referenced, `false` if not.
+---
+---@return boolean|nil has_ref
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function handle:has_ref() end
+
+--- Returns `true` if the handle is active, `false` if it's inactive.
+---
+--- What "active” means depends on the type of handle:
+---
+--- - A `uv_async_t` handle is always active and cannot be deactivated, except by closing it with `uv.close()`.
+---
+--- - A `uv_pipe_t`, `uv_tcp_t`, `uv_udp_t`, etc. handle - basically any handle that deals with I/O - is active when it is doing something that involves I/O, like reading, writing, connecting, accepting new connections, etc.
+---
+--- - A `uv_check_t`, `uv_idle_t`, `uv_timer_t`, etc. handle is active when it has been started with a call to `uv.check_start()`, `uv.idle_start()`, `uv.timer_start()` etc. until it has been stopped with a call to its respective stop function.
+---
+---@return boolean|nil active
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function handle:is_active() end
+
+--- Returns `true` if the handle is closing or closed, `false` otherwise.
+---
+--- **Note**: This function should only be used between the initialization of the
+--- handle and the arrival of the close callback.
+---
+---@return boolean|nil closing
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function handle:is_closing() end
+
+--- Gets or sets the size of the receive buffer that the operating system uses for
+--- the socket.
+---
+--- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size.
+---
+--- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP
+--- handles on Windows.
+---
+--- **Note**: Linux will set double the size and return double the size of the
+--- original set value.
+---
+---@param  size       integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(self):(current_size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+---@overload fun(self, size: 0):(current_size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+function handle:recv_buffer_size(size) end
+
+--- Reference the given handle.
+---
+--- References are idempotent, that is, if a handle is already referenced calling this function again will have no effect.
+function handle:ref() end
+
+
+--- Gets or sets the size of the send buffer that the operating system uses for the
+--- socket.
+---
+--- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size.
+---
+--- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP
+--- handles on Windows.
+---
+--- **Note**: Linux will set double the size and return double the size of the
+--- original set value.
+---
+---@param  size        integer
+---@return integer|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+---
+---@overload fun(self):(current_size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+---@overload fun(self, size: 0):(current_size:integer|nil, err:uv.error.message|nil, err_name:uv.error.name|nil)
+function handle:send_buffer_size(size) end
+
+
+--- Un-reference the given handle. References are idempotent, that is, if a handle
+--- is not referenced calling this function again will have no effect.
+function handle:unref() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_idle_t.lua b/meta/3rd/luv/library/uv_idle_t.lua
new file mode 100644
index 000000000..22368193f
--- /dev/null
+++ b/meta/3rd/luv/library/uv_idle_t.lua
@@ -0,0 +1,38 @@
+---@meta
+
+--- Idle handles will run the given callback once per loop iteration, right before
+--- the `uv_prepare_t` handles.
+---
+--- **Note**: The notable difference with prepare handles is that when there are
+--- active idle handles, the loop will perform a zero timeout poll instead of
+--- blocking for I/O.
+---
+--- **Warning**: Despite the name, idle handles will get their callbacks called on
+--- every loop iteration, not when the loop is actually "idle".
+---
+--- ```lua
+--- local idle = uv.new_idle()
+--- idle:start(function()
+---   print("Before I/O polling, no blocking")
+--- end)
+--- ```
+---
+---@class uv.uv_idle_t : uv.uv_handle_t
+---
+local idle
+
+--- Start the handle with the given callback.
+---
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function idle:start(callback) end
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@param  check      any
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function idle:stop(check) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_pipe_t.lua b/meta/3rd/luv/library/uv_pipe_t.lua
new file mode 100644
index 000000000..0efbb04fb
--- /dev/null
+++ b/meta/3rd/luv/library/uv_pipe_t.lua
@@ -0,0 +1,103 @@
+---@meta
+
+--- Pipe handles provide an abstraction over local domain sockets on Unix and named pipes on Windows.
+---
+--- ```lua
+--- local pipe = uv.new_pipe(false)
+---
+--- pipe:bind('/tmp/sock.test')
+---
+--- pipe:listen(128, function()
+---   local client = uv.new_pipe(false)
+---   pipe:accept(client)
+---   client:write("hello!\n")
+---   client:close()
+--- end)
+--- ```
+---
+---@class uv.uv_pipe_t : uv.uv_stream_t
+---
+local pipe
+
+--- Bind the pipe to a file path (Unix) or a name (Windows).
+---
+--- **Note**: Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes,
+--- typically between 92 and 108 bytes.
+---
+---@param  name       string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:bind(name) end
+
+--- Alters pipe permissions, allowing it to be accessed from processes run by different users.
+---
+--- Makes the pipe writable or readable by all users. `flags` are: `"r"`, `"w"`, `"rw"`, or `"wr"`
+--- where `r` is `READABLE` and `w` is `WRITABLE`.
+---
+--- This function is blocking.
+---
+---@param  flags      uv.pipe_chmod.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:chmod(flags) end
+
+--- Connect to the Unix domain socket or the named pipe.
+---
+--- **Note**: Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes,
+--- typically between 92 and 108 bytes.
+---
+---@param  name                string
+---@param  callback?           uv.pipe_connect.callback
+---@return uv.uv_connect_t|nil conn
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:connect(name, callback) end
+
+--- Get the name of the Unix domain socket or the named pipe to which the handle is
+--- connected.
+---
+---@return string|nil peername
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:getpeername() end
+
+--- Get the name of the Unix domain socket or the named pipe.
+---
+---@return string|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:getsockname() end
+
+--- Open an existing file descriptor or `uv_handle_t` as a pipe.
+---
+--- **Note**: The file descriptor is set to non-blocking mode.
+---
+---@param  fd         integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function pipe:open(fd) end
+
+--- Returns the pending pipe count for the named pipe.
+---
+---@return integer count
+function pipe:pending_count() end
+
+--- Set the number of pending pipe instance handles when the pipe server is waiting
+--- for connections.
+---
+--- **Note**: This setting applies to Windows only.
+---
+---@param count integer
+function pipe:pending_instances(count) end
+
+--- Used to receive handles over IPC pipes.
+---
+--- First - call `uv.pipe_pending_count()`, if it's > 0 then initialize a handle of
+--- the given type, returned by `uv.pipe_pending_type()` and call
+--- `uv.accept(pipe, handle)`.
+---
+---@return string
+function pipe:pending_type() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_poll_t.lua b/meta/3rd/luv/library/uv_poll_t.lua
new file mode 100644
index 000000000..313361528
--- /dev/null
+++ b/meta/3rd/luv/library/uv_poll_t.lua
@@ -0,0 +1,57 @@
+---@meta
+
+--- Poll handles are used to watch file descriptors for readability and writability,
+--- similar to the purpose of [poll(2)](http://linux.die.net/man/2/poll).
+---
+--- The purpose of poll handles is to enable integrating external libraries that
+--- rely on the event loop to signal it about the socket status changes, like c-ares
+--- or libssh2. Using `uv_poll_t` for any other purpose is not recommended;
+--- `uv_tcp_t`, `uv_udp_t`, etc. provide an implementation that is faster and more
+--- scalable than what can be achieved with `uv_poll_t`, especially on Windows.
+---
+--- It is possible that poll handles occasionally signal that a file descriptor is
+--- readable or writable even when it isn't. The user should therefore always be
+--- prepared to handle EAGAIN or equivalent when it attempts to read from or write
+--- to the fd.
+---
+--- It is not okay to have multiple active poll handles for the same socket, this
+--- can cause libuv to busyloop or otherwise malfunction.
+---
+--- The user should not close a file descriptor while it is being polled by an
+--- active poll handle. This can cause the handle to report an error, but it might
+--- also start polling another socket. However the fd can be safely closed
+--- immediately after a call to `poll:stop()` or `uv.close()`.
+---
+--- **Note**: On windows only sockets can be polled with poll handles. On Unix any
+--- file descriptor that would be accepted by poll(2) can be used.
+---
+---@class uv.uv_poll_t : uv.uv_handle_t
+local poll
+
+--- Starts polling the file descriptor.
+---
+--- `events` are: `"r"`, `"w"`, `"rw"`, `"d"`, `"rd"`, `"wd"`, `"rwd"`, `"p"`, `"rp"`, `"wp"`, `"rwp"`, `"dp"`, `"rdp"`, `"wdp"`, or `"rwdp"` where `r` is `READABLE`, `w` is `WRITABLE`, `d` is `DISCONNECT`, and `p` is `PRIORITIZED`.
+---
+--- As soon as an event is detected the callback will be called with status set to 0, and the detected events set on the events field.
+---
+--- The user should not close the socket while the handle is active. If the user
+--- does that anyway, the callback may be called reporting an error status, but this
+--- is not guaranteed.
+---
+--- **Note** Calling `poll:start()` on a handle that is already active is fine.
+--- Doing so will update the events mask that is being watched for.
+---
+---@param  events     uv.poll.eventspec
+---@param  callback   uv.poll_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function poll:start(events, callback) end
+
+
+--- Stop polling the file descriptor, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function poll:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_prepare_t.lua b/meta/3rd/luv/library/uv_prepare_t.lua
new file mode 100644
index 000000000..91e3231b8
--- /dev/null
+++ b/meta/3rd/luv/library/uv_prepare_t.lua
@@ -0,0 +1,30 @@
+---@meta
+
+--- Prepare handles will run the given callback once per loop iteration, right
+--- before polling for I/O.
+---
+--- ```lua
+--- local prepare = uv.new_prepare()
+--- prepare:start(function()
+---   print("Before I/O polling")
+--- end)
+--- ```
+---
+---@class uv.uv_prepare_t : uv.uv_handle_t
+---
+local prepare
+
+--- Start the handle with the given callback.
+---
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function prepare:start(callback) end
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function prepare:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_process_t.lua b/meta/3rd/luv/library/uv_process_t.lua
new file mode 100644
index 000000000..7329df3cb
--- /dev/null
+++ b/meta/3rd/luv/library/uv_process_t.lua
@@ -0,0 +1,22 @@
+---@meta
+
+--- Process handles will spawn a new process and allow the user to control it and
+--- establish communication channels with it using streams.
+---
+---@class uv.uv_process_t : uv.uv_handle_t
+local process
+
+--- Returns the handle's pid.
+---
+---@return integer pid
+function process:get_pid() end
+
+--- Sends the specified signal to the given process handle.
+---
+--- Check the documentation on `uv_signal_t` for signal support, specially on Windows.
+---
+---@param  signum     integer|string
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function process:kill(signum) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_req_t.lua b/meta/3rd/luv/library/uv_req_t.lua
new file mode 100644
index 000000000..b39dc317f
--- /dev/null
+++ b/meta/3rd/luv/library/uv_req_t.lua
@@ -0,0 +1,25 @@
+---@meta
+
+--- Base request
+---
+--- `uv_req_t` is the base type for all libuv request types.
+---
+---@class uv.uv_req_t : table
+---
+local req
+
+--- Cancel a pending request. Fails if the request is executing or has finished
+--- executing. Only cancellation of `uv_fs_t`, `uv_getaddrinfo_t`,
+--- `uv_getnameinfo_t` and `uv_work_t` requests is currently supported.
+---
+---@return 0|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function req:cancel() end
+
+--- Returns the name of the struct for a given request (e.g. `"fs"` for `uv_fs_t`)
+--- and the libuv enum integer for the request's type (`uv_req_type`).
+---
+---@return string type
+---@return integer enum
+function req:get_type() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_signal_t.lua b/meta/3rd/luv/library/uv_signal_t.lua
new file mode 100644
index 000000000..69be819e3
--- /dev/null
+++ b/meta/3rd/luv/library/uv_signal_t.lua
@@ -0,0 +1,69 @@
+---@meta
+
+--- Signal handles implement Unix style signal handling on a per-event loop bases.
+---
+--- **Unix Notes:**
+---
+--- - SIGKILL and SIGSTOP are impossible to catch.
+---
+--- - Handling SIGBUS, SIGFPE, SIGILL or SIGSEGV via libuv results into undefined behavior.
+---
+--- - SIGABRT will not be caught by libuv if generated by abort(), e.g. through assert().
+---
+--- - On Linux SIGRT0 and SIGRT1 (signals 32 and 33) are used by the NPTL pthreads library to manage threads. Installing watchers for those signals will lead to unpredictable behavior and is strongly discouraged. Future versions of libuv may simply reject them.
+---
+--- **Windows Notes:**
+---
+--- Reception of some signals is emulated on Windows:
+--- - SIGINT is normally delivered when the user presses CTRL+C. However, like on Unix, it is not generated when terminal raw mode is enabled.
+---
+--- - SIGBREAK is delivered when the user pressed CTRL + BREAK.
+---
+--- - SIGHUP is generated when the user closes the console window. On SIGHUP the program is given approximately 10 seconds to perform cleanup. After that Windows will unconditionally terminate it.
+---
+--- - SIGWINCH is raised whenever libuv detects that the console has been resized. SIGWINCH is emulated by libuv when the program uses a uv_tty_t handle to write to the console. SIGWINCH may not always be delivered in a timely manner; libuv will only detect size changes when the cursor is being moved. When a readable `uv_tty_t` handle is used in raw mode, resizing the console buffer will also trigger a SIGWINCH signal.
+---
+--- - Watchers for other signals can be successfully created, but these signals are never received. These signals are: SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM and SIGKILL.
+---
+--- - Calls to raise() or abort() to programmatically raise a signal are not detected by libuv; these will not trigger a signal watcher.
+---
+---
+--- ```lua
+--- -- Create a new signal handler
+--- local signal = uv.new_signal()
+--- -- Define a handler function
+--- uv.signal_start(signal, "sigint", function(signal)
+---   print("got " .. signal .. ", shutting down")
+---   os.exit(1)
+--- end)
+--- ```
+---
+---@class uv.uv_signal_t : uv.uv_handle_t
+---
+local signal
+
+--- Start the handle with the given callback, watching for the given signal.
+---
+---@param  signum     integer|string
+---@param  callback   uv.signal_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function signal:start(signum, callback) end
+
+--- Same functionality as `signal:start()` but the signal handler is reset the moment the signal is received.
+---
+---@param  signum     integer|string
+---@param  callback   uv.signal_start_oneshot.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function signal:start_oneshot(signum, callback) end
+
+
+--- Stop the handle, the callback will no longer be called.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function signal:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_stream_t.lua b/meta/3rd/luv/library/uv_stream_t.lua
new file mode 100644
index 000000000..2de883b73
--- /dev/null
+++ b/meta/3rd/luv/library/uv_stream_t.lua
@@ -0,0 +1,173 @@
+---@meta
+
+--- Stream handles provide an abstraction of a duplex communication channel.
+--- `uv_stream_t` is an abstract type, libuv provides 3 stream implementations
+--- in the form of `uv_tcp_t`, `uv_pipe_t` and `uv_tty_t`.
+---
+---@class uv.uv_stream_t : uv.uv_handle_t
+---
+local stream
+
+--- This call is used in conjunction with `uv.listen()` to accept incoming
+--- connections. Call this function after receiving a callback to accept the
+--- connection.
+---
+--- When the connection callback is called it is guaranteed that this function
+--- will complete successfully the first time. If you attempt to use it more than
+--- once, it may fail. It is suggested to only call this function once per
+--- connection call.
+---
+--- ```lua
+--- server:listen(128, function (err)
+---   local client = uv.new_tcp()
+---   server:accept(client)
+--- end)
+--- ```
+---
+---@param  client_stream uv.uv_stream_t
+---@return 0|nil         success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:accept(client_stream) end
+
+--- Returns the stream's write queue size.
+---
+---@return integer size
+function stream:get_write_queue_size() end
+
+--- Returns `true` if the stream is readable, `false` otherwise.
+---
+---@return boolean readable
+function stream:is_readable() end
+
+--- Returns `true` if the stream is writable, `false` otherwise.
+---
+---@return boolean writable
+function stream:is_writable() end
+
+--- Start listening for incoming connections.
+---
+--- `backlog` indicates the number of connections the kernel might queue, same as `listen(2)`.
+---
+--- When a new incoming connection is received the callback is called.
+---
+---@param  backlog    integer
+---@param  callback   uv.listen.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:listen(backlog, callback) end
+
+--- Read data from an incoming stream.
+---
+--- The callback will be made several times until there is no more data to read or `stream:read_stop()` is called.
+---
+--- When we've reached EOF, `data` will be `nil`.
+---
+--- ```lua
+--- stream:read_start(function (err, chunk)
+---   if err then
+---     -- handle read error
+---   elseif chunk then
+---     -- handle data
+---   else
+---     -- handle disconnect
+---   end
+--- end)
+--- ```
+---
+---@param  callback   uv.read_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:read_start(callback) end
+
+--- Stop reading data from the stream.
+---
+--- The read callback will no longer be called.
+---
+--- This function is idempotent and may be safely called on a stopped stream.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:read_stop() end
+
+--- Enable or disable blocking mode for a stream.
+---
+--- When blocking mode is enabled all writes complete synchronously. The interface
+--- remains unchanged otherwise, e.g. completion or failure of the operation will
+--- still be reported through a callback which is made asynchronously.
+---
+--- **Warning**: Relying too much on this API is not recommended. It is likely to
+--- change significantly in the future. Currently this only works on Windows and
+--- only for `uv_pipe_t` handles. Also libuv currently makes no ordering guarantee
+--- when the blocking mode is changed after write requests have already been
+--- submitted. Therefore it is recommended to set the blocking mode immediately
+--- after opening or creating the stream.
+---
+---@param  blocking   boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:set_blocking(blocking) end
+
+--- Shutdown the outgoing (write) side of a duplex stream. It waits for pending
+--- write requests to complete. The callback is called after shutdown is complete.
+---
+---@param  callback?            uv.shutdown.callback
+---@return uv.uv_shutdown_t|nil shutdown
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:shutdown(callback) end
+
+--- Same as `stream:write()`, but won't queue a write request if it can't be completed
+--- immediately.
+---
+--- Will return number of bytes written (can be less than the supplied buffer size).
+---
+---@param  data        uv.buffer
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:try_write(data) end
+
+--- Like `stream:write2()`, but with the properties of `stream:try_write()`. Not supported on Windows, where it returns `UV_EAGAIN`.
+---
+--- Will return number of bytes written (can be less than the supplied buffer size).
+---
+---@param  data        uv.buffer
+---@param  send_handle uv.uv_stream_t
+---@return integer|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:try_write2(data, send_handle) end
+
+--- Write data to stream.
+---
+--- `data` can either be a Lua string or a table of strings. If a table is passed
+--- in, the C backend will use writev to send all strings in a single system call.
+---
+--- The optional `callback` is for knowing when the write is complete.
+---
+---@param  data              uv.buffer
+---@param  callback?         uv.write.callback
+---@return uv.uv_write_t|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:write(data, callback) end
+
+--- Extended write function for sending handles over a pipe. The pipe must be
+--- initialized with `ipc` option `true`.
+---
+--- **Note:** `send_handle` must be a TCP socket or pipe, which is a server or a
+--- connection (listening or connected state). Bound sockets or pipes will be
+--- assumed to be servers.
+---
+---@param  data              uv.buffer
+---@param  send_handle       uv.uv_stream_t
+---@param  callback?         uv.write2.callback
+---@return uv.uv_write_t|nil write
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function stream:write2(data, send_handle, callback) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_tcp_t.lua b/meta/3rd/luv/library/uv_tcp_t.lua
new file mode 100644
index 000000000..8dee919f5
--- /dev/null
+++ b/meta/3rd/luv/library/uv_tcp_t.lua
@@ -0,0 +1,112 @@
+---@meta
+
+--- TCP handles are used to represent both TCP streams and servers.
+---
+---@class uv.uv_tcp_t : uv.uv_stream_t
+local tcp
+
+--- Bind the handle to an host and port.
+---
+--- Any `flags` are set with a table with field `ipv6only` equal to `true` or `false`.
+---
+--- When the port is already taken, you can expect to see an `EADDRINUSE` error
+--- from either `tcp:bind()`, `uv.listen()` or `tcp:connect()`. That is, a
+--- successful call to this function does not guarantee that the call to `uv.listen()`
+--- or `tcp:connect()` will succeed as well.
+---
+--- Use a port of `0` to let the OS assign an ephemeral port.  You can look it up
+--- later using `tcp:getsockname()`.
+---
+---@param  addr       string            # must be an IP address and not a hostname
+---@param  port       integer           # set to `0` to allow the OS to assign an ephemeral port
+---@param  flags?     uv.tcp_bind.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:bind(addr, port, flags) end
+
+--- Resets a TCP connection by sending a RST packet. This is accomplished by setting
+--- the SO_LINGER socket option with a linger interval of zero and then calling
+--- `uv.close()`. Due to some platform inconsistencies, mixing of `uv.shutdown()`
+--- and `tcp:close_reset()` calls is not allowed.
+---
+---@param  callback?  function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:close_reset(callback) end
+
+--- Establish an IPv4 or IPv6 TCP connection.
+---
+--- ```lua
+--- local client = uv.new_tcp()
+--- client:connect("127.0.0.1", 8080, function (err)
+---   -- check error and carry on.
+--- end)
+--- ```
+---
+---@param  host                string
+---@param  port                integer
+---@param  callback            uv.tcp_connect.callback
+---@return uv.uv_connect_t|nil conn
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:connect(host, port, callback) end
+
+--- Get the address of the peer connected to the handle.
+---
+---@return uv.socketinfo|nil peername
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:getpeername() end
+
+--- Get the current address to which the handle is bound.
+---
+---@return uv.socketinfo|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:getsockname() end
+
+--- Enable / disable TCP keep-alive.
+---
+---@param  enable     boolean
+---@param  delay?     integer # initial delay, in seconds
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:keepalive(enable, delay) end
+
+--- Enable / disable Nagle's algorithm.
+---
+---@param  enable     boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:nodelay(enable) end
+
+--- Open an existing file descriptor or SOCKET as a TCP handle.
+---
+--- **Note:** The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid stream socket.
+---
+---@param  sock       integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:open(sock) end
+
+--- Enable / disable simultaneous asynchronous accept requests that are queued by
+--- the operating system when listening for new TCP connections.
+---
+--- This setting is used to tune a TCP server for the desired performance. Having
+--- simultaneous accepts can significantly improve the rate of accepting connections
+--- (which is why it is enabled by default) but may lead to uneven load distribution
+--- in multi-process setups.
+---
+---@param  enable     boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tcp:simultaneous_accepts(enable) end
+
+--- **Deprecated:** Please use `uv.stream_get_write_queue_size()` instead.
+function tcp:write_queue_size() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_timer_t.lua b/meta/3rd/luv/library/uv_timer_t.lua
new file mode 100644
index 000000000..64a70418d
--- /dev/null
+++ b/meta/3rd/luv/library/uv_timer_t.lua
@@ -0,0 +1,55 @@
+---@meta
+
+--- Timer handles are used to schedule callbacks to be called in the future.
+---
+---@class uv.uv_timer_t : uv.uv_handle_t
+local timer
+
+--- Stop the timer, and if it is repeating restart it using the repeat value as the
+--- timeout. If the timer has never been started before it raises `EINVAL`.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function timer:again() end
+
+--- Get the timer due value or 0 if it has expired. The time is relative to `uv.now()`.
+---
+--- **Note**: New in libuv version 1.40.0.
+---
+---@return integer
+function timer:get_due_in() end
+
+--- Get the timer repeat value.
+---
+---@return integer
+function timer:get_repeat() end
+
+--- Set the repeat interval value in milliseconds. The timer will be scheduled to
+--- run on the given interval, regardless of the callback execution duration, and
+--- will follow normal timer semantics in the case of a time-slice overrun.
+---
+--- For example, if a 50 ms repeating timer first runs for 17 ms, it will be
+--- scheduled to run again 33 ms later. If other tasks consume more than the 33 ms
+--- following the first timer callback, then the callback will run as soon as
+--- possible.
+---
+---@param repeat_ integer
+function timer:set_repeat(repeat_) end
+
+--- Start the timer. `timeout` and `repeat_` are in milliseconds.
+---
+---@param  timeout    integer # Timeout, in milliseconds. If timeout is zero, the callback fires on the next event loop iteration.
+---@param  repeat_    integer # Repeat interval, in milliseconds. If non-zero, the callback fires after `timeout` milliseconds and then repeatedly after `repeat_` milliseconds.
+---@param  callback   function
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function timer:start(timeout, repeat_, callback) end
+
+--- Stop the timer, the callback will not be called anymore.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function timer:stop() end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_tty_t.lua b/meta/3rd/luv/library/uv_tty_t.lua
new file mode 100644
index 000000000..15e6fe461
--- /dev/null
+++ b/meta/3rd/luv/library/uv_tty_t.lua
@@ -0,0 +1,44 @@
+---@meta
+
+--- TTY handles represent a stream for the console.
+---
+--- ```lua
+--- -- Simple echo program
+--- local stdin = uv.new_tty(0, true)
+--- local stdout = uv.new_tty(1, false)
+---
+--- stdin:read_start(function (err, data)
+---   assert(not err, err)
+---   if data then
+---     stdout:write(data)
+---   else
+---     stdin:close()
+---     stdout:close()
+---   end
+--- end)
+--- ```
+---
+---@class uv.uv_tty_t : uv.uv_stream_t
+local tty
+
+--- Gets the current Window width and height.
+---
+---@return integer|nil    width
+---@return integer|uv.error.message height_or_err
+---@return uv.error.name|nil err_name
+function tty:get_winsize() end
+
+--- Set the TTY using the specified terminal mode.
+---
+--- Parameter `mode` is a C enum with the following values:
+---
+---   - 0 - UV_TTY_MODE_NORMAL: Initial/normal terminal mode
+---   - 1 - UV_TTY_MODE_RAW: Raw input mode (On Windows, ENABLE_WINDOW_INPUT is
+---   also enabled)
+---   - 2 - UV_TTY_MODE_IO: Binary-safe I/O mode for IPC (Unix-only)
+---
+---@param  mode       uv.tty.mode
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function tty:set_mode(mode) end
\ No newline at end of file
diff --git a/meta/3rd/luv/library/uv_udp_t.lua b/meta/3rd/luv/library/uv_udp_t.lua
new file mode 100644
index 000000000..a2dfd6765
--- /dev/null
+++ b/meta/3rd/luv/library/uv_udp_t.lua
@@ -0,0 +1,175 @@
+---@meta
+
+--- UDP handles encapsulate UDP communication for both clients and servers.
+---
+---@class uv.uv_udp_t : uv.uv_handle_t
+local udp
+
+--- Bind the UDP handle to an IP address and port. Any `flags` are set with a table
+--- with fields `reuseaddr` or `ipv6only` equal to `true` or `false`.
+---
+---@param  host       string
+---@param  port       integer
+---@param  flags?     uv.udp_bind.flags
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:bind(host, port, flags) end
+
+
+--- Associate the UDP handle to a remote address and port, so every message sent by
+--- this handle is automatically sent to that destination.
+--
+--- Calling this function with a NULL addr disconnects the handle. Trying to call `udp:connect()` on an already connected handle will result in an `EISCONN` error. Trying to disconnect a handle that is not connected will return an `ENOTCONN` error.
+---
+---@param  host       string
+---@param  port       integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:connect(host, port) end
+
+--- Returns the handle's send queue count.
+---
+---@return integer count
+function udp:get_send_queue_count() end
+
+--- Returns the handle's send queue size.
+---
+---@return integer size
+function udp:get_send_queue_size() end
+
+--- Get the remote IP and port of the UDP handle on connected UDP handles.
+---
+---@return uv.udp.sockname|nil peername
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:getpeername() end
+
+--- Get the local IP and port of the UDP handle.
+---
+---@return uv.udp.sockname|nil sockname
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:getsockname() end
+
+--- Opens an existing file descriptor or Windows SOCKET as a UDP handle.
+---
+--- Unix only: The only requirement of the sock argument is that it follows the
+--- datagram contract (works in unconnected mode, supports sendmsg()/recvmsg(),
+--- etc). In other words, other datagram-type sockets like raw sockets or netlink
+--- sockets can also be passed to this function.
+---
+--- The file descriptor is set to non-blocking mode.
+---
+--- Note: The passed file descriptor or SOCKET is not checked for its type, but
+--- it's required that it represents a valid datagram socket.
+---
+---@param  fd         integer
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:open(fd) end
+
+--- Prepare for receiving data.
+---
+--- If the socket has not previously been bound with `udp:bind()` it is bound to `0.0.0.0` (the "all interfaces" IPv4 address) and a random port number.
+---
+---@param  callback   uv.udp_recv_start.callback
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:recv_start(callback) end
+
+--- Stop listening for incoming datagrams.
+---
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:recv_stop() end
+
+--- Send data over the UDP socket.
+---
+--- If the socket has not previously been bound with `udp:bind()` it will be bound to `0.0.0.0` (the "all interfaces" IPv4 address) and a random port number.
+---
+---@param  data                 uv.buffer
+---@param  host                 string
+---@param  port                 integer
+---@param  callback             uv.udp_send.callback
+---@return uv.uv_udp_send_t|nil bytes
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:send(data, host, port, callback) end
+
+--- Set broadcast on or off.
+---
+---@param  on         boolean
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_broadcast(on) end
+
+--- Set membership for a multicast address.
+---
+---@param  multicast_addr string          # multicast address to set membership for
+---@param  interface_addr string          # interface address
+---@param  membership     "leave"|"join"  # membership intent
+---@return 0|nil          success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_membership(multicast_addr, interface_addr, membership) end
+
+--- Set the multicast interface to send or receive data on.
+---
+---@param interface_addr string
+---@return 0|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_multicast_interface(interface_addr) end
+
+--- Set IP multicast loop flag. Makes multicast packets loop back to local
+--- sockets.
+---
+---@param on boolean
+---@return 0|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_multicast_loop(on) end
+
+--- Set the multicast ttl.
+---
+---@param  ttl        integer # an integer 1 through 255
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_multicast_ttl(ttl) end
+
+--- Set membership for a source-specific multicast group.
+---
+---@param  multicast_addr  string         # multicast address to set membership for
+---@param  interface_addr? string         # interface address
+---@param  source_addr     string         # source address
+---@param  membership      "leave"|"join" # membership intent
+---@return 0|nil           success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_source_membership(multicast_addr, interface_addr, source_addr, membership) end
+
+--- Set the time to live.
+---
+---@param  ttl        integer # integer 1 through 255
+---@return 0|nil      success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:set_ttl(ttl) end
+
+--- Same as `udp:send()`, but won't queue a send request if it can't be
+--- completed immediately.
+---
+---@param  data        uv.buffer
+---@param  host        string
+---@param  port        integer
+---@return integer|nil success
+---@return uv.error.message|nil err
+---@return uv.error.name|nil err_name
+function udp:try_send(data, host, port) end
\ No newline at end of file
diff --git a/meta/3rd/skynet/library/skynet.lua b/meta/3rd/skynet/library/skynet.lua
index f09d996c7..0e4d19dd8 100644
--- a/meta/3rd/skynet/library/skynet.lua
+++ b/meta/3rd/skynet/library/skynet.lua
@@ -471,7 +471,14 @@ function skynet.stat(what)
 end
 
 ---返回任务信息
----@param ret any
+---这里说的任务是指：服务发出了请求，但未收到响应，已挂起的协程
+---当参数ret的值为：
+--- nil: 返回挂起的协程数量
+--- "init": 返回 traceback(init_thread)
+--- table:  所有挂起协程的状态信息
+--- number :代表一个会话ID，显示此会话状态信息
+--- thread: 某个协程的信息
+---@param ret nil|number|table|string
 function skynet.task(ret)
 end
 
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 9b6210e1e..44f62a463 100644
--- a/meta/template/basic.lua
+++ b/meta/template/basic.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta _
 
 ---#DES 'arg'
 ---@type string[]
@@ -8,28 +8,30 @@ arg = {}
 ---@generic T
 ---@param v? T
 ---@param message? any
+---@param ... any
 ---@return T
 ---@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
@@ -76,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'
@@ -128,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
@@ -136,6 +138,7 @@ function newproxy(proxy) end
 ---@version 5.1
 ---#DES 'module'
 ---@param name string
+---@param ...  any
 function module(name, ...) end
 
 ---#DES 'next'
@@ -161,12 +164,14 @@ function pairs(t) end
 ---@param f     async fun(...):...
 ---#end
 ---@param arg1? any
+---@param ...   any
 ---@return boolean success
 ---@return any result
 ---@return any ...
 function pcall(f, arg1, ...) end
 
 ---#DES 'print'
+---@param ... any
 function print(...) end
 
 ---#DES 'rawequal'
@@ -197,7 +202,8 @@ 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
@@ -209,9 +215,46 @@ function select(index, ...) end
 ---@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
+
 ---#DES 'setmetatable'
 ---@param table      table
----@param metatable? table
+---@param metatable? metatable|table
 ---@return table
 function setmetatable(table, metatable) end
 
@@ -229,16 +272,16 @@ 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"'
+---| "cdata"
 ---#end
 
 ---#DES 'type'
@@ -249,18 +292,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
@@ -276,6 +320,7 @@ function xpcall(f, err) end
 ---@param f     async fun(...):...
 ---@param msgh  function
 ---@param arg1? any
+---@param ...   any
 ---@return boolean success
 ---@return any result
 ---@return any ...
diff --git a/meta/template/bit.lua b/meta/template/bit.lua
index 369c5f9d1..c9563f420 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
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 e807f357a..91a3aba3f 100644
--- a/meta/template/coroutine.lua
+++ b/meta/template/coroutine.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta coroutine
 
 ---#DES 'coroutine'
 ---@class coroutinelib
@@ -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
diff --git a/meta/template/debug.lua b/meta/template/debug.lua
index 48ba52d97..9f48ee98c 100644
--- a/meta/template/debug.lua
+++ b/meta/template/debug.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta debug
 
 ---#DES 'debug'
 ---@class debuglib
@@ -45,20 +45,20 @@ 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
@@ -141,9 +141,9 @@ 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)
diff --git a/meta/template/ffi.lua b/meta/template/ffi.lua
index 2ca83dd5c..a9d486578 100644
--- a/meta/template/ffi.lua
+++ b/meta/template/ffi.lua
@@ -1,5 +1,5 @@
 ---#if not JIT then DISABLE() end
----@meta
+---@meta ffi
 
 ---@class ffi.namespace*: table
 ---@field [string] function
diff --git a/meta/template/io.lua b/meta/template/io.lua
index d685128b1..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
@@ -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*
@@ -103,20 +103,20 @@ local file = {}
 
 ---@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
@@ -140,9 +140,9 @@ function file:lines(...) end
 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 d1684a047..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(...):...
 ---@param func       function|boolean
 ---@param recursive? boolean
-function jit.on(func, recursive) end
+function jit.on(func, recursive)
+end
 
 ---@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(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 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 fbe378915..4dbffc6b2 100644
--- a/meta/template/os.lua
+++ b/meta/template/os.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta os
 
 ---#DES 'os'
 ---@class oslib
@@ -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/template/string.buffer.lua b/meta/template/string.buffer.lua
index ee57a1ad7..7285290a4 100644
--- a/meta/template/string.buffer.lua
+++ b/meta/template/string.buffer.lua
@@ -1,5 +1,5 @@
 ---#if not JIT then DISABLE() end
----@meta
+---@meta string.buffer
 
 ---@version JIT
 --- The string buffer library allows high-performance manipulation of string-like data.
diff --git a/meta/template/string.lua b/meta/template/string.lua
index c68e1117c..e23b0803f 100644
--- a/meta/template/string.lua
+++ b/meta/template/string.lua
@@ -1,11 +1,11 @@
----@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 ...
@@ -20,25 +20,32 @@ function string.byte(s, i, j) end
 function string.char(byte, ...) end
 
 ---#DES 'string.dump'
+---#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 any ... 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
@@ -46,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
@@ -70,20 +77,20 @@ 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 any ...
 ---@nodiscard
@@ -93,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
@@ -107,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
@@ -147,7 +155,7 @@ function string.sub(s, i, j) end
 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 90efde008..a2e9580b4 100644
--- a/meta/template/table.lua
+++ b/meta/template/table.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta table
 
 ---#DES 'table'
 ---@class tablelib
@@ -37,7 +37,7 @@ function table.maxn(table) end
 ---@return table a2
 function table.move(a1, f, e, t, a2) end
 
----@version >5.2
+---@version >5.2, JIT
 ---#DES 'table.pack'
 ---@return table
 ---@nodiscard
@@ -55,7 +55,7 @@ function table.remove(list, pos) end
 ---@param comp? fun(a: T, b: T):boolean
 function table.sort(list, comp) end
 
----@version >5.2
+---@version >5.2, JIT
 ---#DES 'table.unpack'
 ---@generic T
 ---@param list T[]
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 cccee99af..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'
diff --git a/meta/whimsical/basic.lua b/meta/whimsical/basic.lua
new file mode 100644
index 000000000..8f25d19f1
--- /dev/null
+++ b/meta/whimsical/basic.lua
@@ -0,0 +1,38 @@
+---@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]
+---@error => args[1].istruly
+---@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
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>: { [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..2cfe865f8
--- /dev/null
+++ b/meta/whimsical/rule.lua
@@ -0,0 +1,122 @@
+local cat
+
+cat.rule.inner.default = function (self)
+    self.istruly = true
+    self.truly   = self
+    self.falsy   = cat.class 'never'
+    self.view    = self.name
+end
+
+cat.rule.inner.never = function (self)
+    self.istruly = nil
+end
+
+cat.rule.inner.any = function (self)
+    self.istruly = nil
+    self.truly   = cat.class 'truly'
+    self.falsy   = cat.boolean(false) | cat.class 'nil'
+end
+
+cat.rule.inner['nil'] = function (self)
+    self.istruly = false
+    self.truly   = cat.class 'never'
+    self.falsy   = self
+end
+
+cat.rule.inner.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.inner.number = function (self)
+    self.istruly = true
+    self.truly   = self
+    self.falsy   = cat.class 'never'
+    self.view    = tostring(self.value)
+end
+
+cat.rule.inner.integer = function (self)
+    self.istruly = true
+    self.truly   = self
+    self.falsy   = cat.class 'never'
+    self.view    = tostring(self.value)
+end
+
+cat.rule.inner.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.inner.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
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/work.lua b/script/brave/work.lua
index 3a565e084..9eb756eb1 100644
--- a/script/brave/work.lua
+++ b/script/brave/work.lua
@@ -1,6 +1,6 @@
 local brave   = require 'brave.brave'
 
-brave.on('loadProto', function ()
+brave.on('loadProtoByStdio', function ()
     local jsonrpc = require 'jsonrpc'
     while true do
         local proto, err = jsonrpc.decode(io.read)
@@ -13,6 +13,63 @@ brave.on('loadProto', function ()
     end
 end)
 
+brave.on('loadProtoBySocket', function (param)
+    local jsonrpc = require 'jsonrpc'
+    local socket  = require 'bee.socket'
+    local util    = require 'utility'
+    local rfd = socket.fd(param.rfd)
+    local wfd = socket.fd(param.wfd)
+    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)
+
+    while true do
+        local rd = socket.select({rfd, wfd}, nil, 10)
+        if not rd or #rd == 0 then
+            goto continue
+        end
+        if util.arrayHas(rd, wfd) then
+            local needSend = wfd:recv()
+            if needSend then
+                rfd:send(needSend)
+            elseif needSend == nil then
+                error('socket closed!')
+            end
+        end
+        if util.arrayHas(rd, rfd) then
+            local recved = rfd:recv()
+            if recved then
+                buf = buf .. recved
+            elseif recved == nil then
+                error('socket closed!')
+            end
+            coroutine.resume(parser)
+        end
+        ::continue::
+    end
+end)
+
 brave.on('timer', function (time)
     local thread = require 'bee.thread'
     while true do
diff --git a/script/cli/check.lua b/script/cli/check.lua
index 37b6ad156..323ea74bd 100644
--- a/script/cli/check.lua
+++ b/script/cli/check.lua
@@ -8,6 +8,10 @@ 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 'vm'
 
 lang(LOCALE)
 
@@ -16,9 +20,10 @@ 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
 
@@ -48,6 +53,8 @@ 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'))
@@ -60,7 +67,7 @@ lclient():start(function (client)
             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 max  = #uris
diff --git a/script/cli/doc.lua b/script/cli/doc.lua
index e44489273..c643deeaa 100644
--- a/script/cli/doc.lua
+++ b/script/cli/doc.lua
@@ -12,24 +12,11 @@ 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'
 
-lang(LOCALE)
-
-if type(DOC) ~= 'string' then
-    print(lang.script('CLI_CHECK_ERROR_TYPE', type(DOC)))
-    return
-end
-
-local rootUri = furi.encode(DOC)
-if not rootUri then
-    print(lang.script('CLI_CHECK_ERROR_URI', DOC))
-    return
-end
-
-util.enableCloseFunction()
-
-local lastClock = os.clock()
-local results = {}
+local export = {}
 
 ---@async
 local function packObject(source, mark)
@@ -70,7 +57,14 @@ local function packObject(source, mark)
                     new.returns[i] = packObject(rtn)
                 end
             end
-            new['view'] = getLabel(source)
+            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)
         end
         if source.type == 'doc.type.table' then
             new['fields'] = packObject(source.fields, mark)
@@ -89,7 +83,7 @@ local function packObject(source, mark)
         if source.bindDocs then
             new['desc'] = getDesc(source)
         end
-        new['view'] = new['view'] or vm.getInfer(source):view(rootUri)
+        new['view'] = new['view'] or vm.getInfer(source):view(ws.rootUri)
     end
     return new
 end
@@ -112,17 +106,19 @@ end
 
 ---@async
 ---@param global vm.global
-local function collect(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,
         defines = {},
         fields  = {},
     }
-    for _, set in ipairs(global:getSets(rootUri)) do
+    for _, set in ipairs(global:getSets(ws.rootUri)) do
         local uri = guide.getUri(set)
         if files.isLibrary(uri) then
             goto CONTINUE
@@ -149,7 +145,7 @@ local function collect(global)
     results[#results+1] = result
     ---@async
     ---@diagnostic disable-next-line: not-yieldable
-    vm.getClassFields(rootUri, global, nil, function (source)
+    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
@@ -160,7 +156,7 @@ local function collect(global)
             if source.field.type == 'doc.field.name' then
                 field.name = source.field[1]
             else
-                field.name = ('[%s]'):format(vm.viewObject(source.field, rootUri))
+                field.name = ('[%s]'):format(vm.getInfer(source.field):view(ws.rootUri))
             end
             field.type    = source.type
             field.file    = guide.getUri(source)
@@ -219,46 +215,153 @@ local function collect(global)
 end
 
 ---@async
-lclient():start(function (client)
-    client:registerFakers()
-
-    client:initialize {
-        rootUri = rootUri,
+---@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)
+        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
 
-    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 globals = vm.getGlobals 'type'
+---@async
+---@param callback fun(i, max)
+function export.export(outputPath, callback)
+    local results = {}
+    local globals = vm.getAllGlobals()
 
-    local max  = #globals
-    for i, global in ipairs(globals) do
-        collect(global)
-        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)
+    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
-    io.write('\x0D')
 
     table.sort(results, function (a, b)
         return a.name < b.name
     end)
-end)
 
-local outpath = LOGPATH .. '/doc.json'
-jsonb.supportSparseArray = true
-util.saveFile(outpath, jsonb.beautify(results))
+    local docPath = outputPath .. '/doc.json'
+    jsonb.supportSparseArray = true
+    util.saveFile(docPath, jsonb.beautify(results))
+
+    local mdPath = doc2md.buildMD(outputPath)
+    return docPath, mdPath
+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 _ <close> = function ()
+        config.set(ws.rootUri, 'Lua.hover.expandAlias', expandAlias)
+    end
+
+    await.sleep(0.1)
+
+    local prog <close> = 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
+
+function export.runCLI()
+    lang(LOCALE)
+
+    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(LOGPATH, 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
 
-require 'cli.doc2md'
+return export
diff --git a/script/cli/doc2md.lua b/script/cli/doc2md.lua
index 8c0956f19..70c1b2a08 100644
--- a/script/cli/doc2md.lua
+++ b/script/cli/doc2md.lua
@@ -5,29 +5,49 @@ local jsonc    = require 'jsonc'
 local util     = require 'utility'
 local markdown = require 'provider.markdown'
 
-local doc = jsonc.decode_jsonc(util.loadFile(LOGPATH .. '/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()
-    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()
+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
-    md:splitLine()
+
+    local mdPath = outputPath .. '/doc.md'
+
+    util.saveFile(mdPath, md:string())
+
+    return mdPath
 end
 
-util.saveFile(LOGPATH .. '/doc.md', md:string())
+return export
diff --git a/script/cli/init.lua b/script/cli/init.lua
index ad5cc3fe0..b5a9f86d0 100644
--- a/script/cli/init.lua
+++ b/script/cli/init.lua
@@ -9,6 +9,6 @@ if _G['CHECK'] then
 end
 
 if _G['DOC'] then
-    require 'cli.doc'
+    require 'cli.doc' .runCLI()
     os.exit(0, true)
 end
diff --git a/script/client.lua b/script/client.lua
index 473c748bf..a8eda9b87 100644
--- a/script/client.lua
+++ b/script/client.lua
@@ -59,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
@@ -109,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
@@ -128,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
@@ -270,15 +273,16 @@ local function searchPatchInfo(cfg, rawKey)
     return searchOnce(rawKey, '', cfg)
         or searchOnce(rawKey:gsub('^Lua%.', ''), '', cfg)
         or {
-            key   = '/' .. rawKey,
+            key   = '/' .. rawKey:gsub('^Lua%.', ''),
             value = nil,
         }
 end
 
+---@param uri uri
 ---@param cfg table
 ---@param change config.change
 ---@return json.patch?
-local function makeConfigPatch(cfg, change)
+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
@@ -288,10 +292,10 @@ local function makeConfigPatch(cfg, change)
                 value = change.value,
             }
         else
-            return makeConfigPatch(cfg, {
+            return makeConfigPatch(uri, cfg, {
                 action = 'set',
                 key    = change.key,
-                value  = { change.value },
+                value  = config.get(uri, change.key),
             })
         end
     elseif change.action == 'set' then
@@ -309,42 +313,44 @@ local function makeConfigPatch(cfg, change)
             }
         end
     elseif change.action == 'prop' then
-        if type(info.value) == 'table' and #info.value == 0 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(cfg, {
+            return makeConfigPatch(uri, cfg, {
                 action = 'set',
                 key    = change.key,
-                value  = { [change.prop] = change.value },
+                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(path, changes)
+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 text
+        return nil
     end
     if type(res) ~= 'table' then
         res = {}
     end
     ---@cast res table
     for _, change in ipairs(changes) do
-        local patch = makeConfigPatch(res, change)
+        local patch = makeConfigPatch(uri, res, change)
         if patch then
             text = jsone.edit(text, patch, { indent = '    ' })
         end
@@ -352,28 +358,49 @@ local function editConfigJson(path, changes)
     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
+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 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
-    local newJson = editConfigJson(path, validChanges)
+    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
 
@@ -381,28 +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 validChanges = getValidChanges(uri, finalChanges)
     if #validChanges == 0 then
+        log.info('No valid changes')
         return false
     end
-    local newJson = editConfigJson(path, validChanges)
+    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, newJson)
+    log.info('Apply changes to .luarc.json', inspect(validChanges))
+    removeAppliedChanges(finalChanges, validChanges)
     return true
 end
 
@@ -410,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
@@ -422,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
 
@@ -436,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[]
@@ -482,28 +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
-            if tryModifySpecifiedConfig(nil, finalChanges) then
-                return
-            end
+            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
@@ -591,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/loader.lua b/script/config/loader.lua
index 8a53d073e..391424d53 100644
--- a/script/config/loader.lua
+++ b/script/config/loader.lua
@@ -54,7 +54,7 @@ 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
diff --git a/script/config/template.lua b/script/config/template.lua
index ea79c2d33..436f5e1a4 100644
--- a/script/config/template.lua
+++ b/script/config/template.lua
@@ -210,6 +210,7 @@ local template = {
                                                     'assert',
                                                     'error',
                                                     'type',
+                                                    'os.exit',
                                                 }
                                             ),
     ['Lua.runtime.meta']                    = Type.String >> '${version} ${language} ${encoding}',
@@ -318,7 +319,6 @@ local template = {
     ['Lua.workspace.library']               = Type.Array(Type.String),
     ['Lua.workspace.checkThirdParty']       = Type.Boolean >> true,
     ['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',
@@ -374,6 +374,7 @@ 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)
                                             >> {},
@@ -384,7 +385,8 @@ local template = {
                                                 auto_complete_table_sep = "true"
                                             },
     ['Lua.spell.dict']                      = Type.Array(Type.String),
-    ['Lua.telemetry.enable']                = Type.Or(Type.Boolean >> false, Type.Nil) >> nil,
+    ['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,
@@ -395,8 +397,17 @@ local template = {
     ['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 4f15b335c..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
@@ -487,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
@@ -676,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
@@ -688,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<integer, codeLens.resolving>
+    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..a96cc918a 100644
--- a/script/core/command/autoRequire.lua
+++ b/script/core/command/autoRequire.lua
@@ -135,6 +135,7 @@ return function (data)
     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 +150,13 @@ 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)
+    applyAutoRequire(uri, offset, name, requireName, fmt)
 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/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/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/completion/auto-require.lua b/script/core/completion/auto-require.lua
index 74fd2e1e6..3139b9116 100644
--- a/script/core/completion/auto-require.lua
+++ b/script/core/completion/auto-require.lua
@@ -48,16 +48,24 @@ function m.check(state, word, position, callback)
         local infos = rpath.getVisiblePath(uri, path)
         local testedStem = { }
         for _, sr in ipairs(infos) do
-            local pattern = sr.searcher
-                :gsub("(%p)", "%%%1")
-                :gsub("%%%?", "(.-)")
+            local stemName
+            if sr.searcher == '[[meta]]' then
+                stemName = sr.name
+            else
+                local pattern = sr.searcher
+                    : gsub("(%p)", "%%%1")
+                    : gsub("%%%?", "(.-)")
 
-            local stemName = relativePath
-                :match(pattern)
-                :match("[%a_][%w_]*$")
+                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
+                if not stemName or testedStem[stemName] then
+                    goto INNER_CONTINUE
+                end
             end
             testedStem[stemName] = true
 
diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua
index cbc8cd078..5a61919cf 100644
--- a/script/core/completion/completion.lua
+++ b/script/core/completion/completion.lua
@@ -83,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
@@ -101,13 +101,47 @@ 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
 
@@ -409,6 +443,7 @@ local function checkFieldFromFieldToIndex(state, name, src, parent, word, startP
         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)
@@ -425,12 +460,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,7 +504,8 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent
     local kind = define.CompletionItemKind.Field
     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
@@ -482,6 +513,7 @@ 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,
@@ -507,7 +539,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)
@@ -534,6 +566,10 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o
     local funcs  = {}
     local count  = 0
     for _, src in ipairs(refs) do
+        if count > 100 then
+            results.incomplete = true
+            break
+        end
         local _, name = vm.viewKey(src, state.uri)
         if not name then
             goto CONTINUE
@@ -545,7 +581,7 @@ 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
@@ -571,7 +607,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
@@ -586,18 +622,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
@@ -1184,6 +1251,46 @@ 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
@@ -1212,7 +1319,7 @@ local function insertEnum(state, pos, src, enums, isInArray, mark)
     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,
         }
@@ -1249,7 +1356,11 @@ local function insertEnum(state, pos, src, enums, isInArray, mark)
     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
@@ -1505,14 +1616,13 @@ local function checkTableLiteralField(state, position, tbl, fields, results)
         end
     end
     if left then
-        local hasResult = false
+        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
-                hasResult = true
-                results[#results+1] = {
+                local res = {
                     label      = guide.getKeyName(field),
                     kind       = define.CompletionItemKind.Property,
                     id         = stack(field, function (newField) ---@async
@@ -1522,9 +1632,20 @@ local function checkTableLiteralField(state, position, tbl, fields, results)
                         }
                     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
-        return hasResult
+        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
 
@@ -1537,6 +1658,7 @@ local function tryCallArg(state, position, results)
     if arg and arg.type == 'function' then
         return
     end
+    ---@diagnostic disable-next-line: missing-fields
     local node = vm.compileCallArg({ type = 'dummyarg' }, call, argIndex)
     if not node then
         return
@@ -1553,20 +1675,15 @@ local function tryCallArg(state, position, results)
 end
 
 local function tryTable(state, position, results)
-    local source = findNearestTableField(state, position)
-    if not source then
+    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
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 11da994a6..3619916e6 100644
--- a/script/core/definition.lua
+++ b/script/core/definition.lua
@@ -205,7 +205,9 @@ return function (uri, offset)
         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 566f4a271..8472e87c5 100644
--- a/script/core/diagnostics/assign-type-mismatch.lua
+++ b/script/core/diagnostics/assign-type-mismatch.lua
@@ -61,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
diff --git a/script/core/diagnostics/cast-local-type.lua b/script/core/diagnostics/cast-local-type.lua
index 1b1c84324..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
diff --git a/script/core/diagnostics/cast-type-mismatch.lua b/script/core/diagnostics/cast-type-mismatch.lua
index c04834596..d86443c57 100644
--- a/script/core/diagnostics/cast-type-mismatch.lua
+++ b/script/core/diagnostics/cast-type-mismatch.lua
@@ -16,13 +16,13 @@ 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)
diff --git a/script/core/diagnostics/duplicate-set-field.lua b/script/core/diagnostics/duplicate-set-field.lua
index 9d6f10ce6..a4b205dda 100644
--- a/script/core/diagnostics/duplicate-set-field.lua
+++ b/script/core/diagnostics/duplicate-set-field.lua
@@ -11,6 +11,22 @@ local sourceTypes = {
     '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 state = files.getState(uri)
@@ -18,6 +34,10 @@ return function (uri, callback)
         return
     end
 
+    if vm.isMetaFile(uri) then
+        return
+    end
+
     ---@async
     guide.eachSourceTypes(state.ast, sourceTypes, function (src)
         await.delay()
@@ -29,6 +49,7 @@ return function (uri, callback)
         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
@@ -39,6 +60,10 @@ return function (uri, callback)
             and def.type ~= 'setindex' then
                 goto CONTINUE
             end
+            local defTopBlock = getTopFunctionOfIf(def)
+            if uri == guide.getUri(def) and myTopBlock ~= defTopBlock then
+                goto CONTINUE
+            end
             local defValue = vm.getObjectValue(def)
             if not defValue or defValue.type ~= 'function' then
                 goto CONTINUE
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/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/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.lua b/script/core/diagnostics/missing-return.lua
index 7333e5e3b..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
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 607d4b0ec..acbf9c8c2 100644
--- a/script/core/diagnostics/param-type-mismatch.lua
+++ b/script/core/diagnostics/param-type-mismatch.lua
@@ -32,26 +32,28 @@ end
 
 ---@param funcNode vm.node
 ---@param i integer
+---@param uri uri
 ---@return vm.node?
-local function getDefNode(funcNode, i)
+local function getDefNode(funcNode, i, uri)
     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]
+    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
-
-                expandGenerics(defNode)
             end
         end
     end
     if defNode:isEmpty() then
         return nil
     end
+
+    expandGenerics(defNode)
+
     return defNode
 end
 
@@ -87,19 +89,17 @@ 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
-                goto CONTINUE
-            end
             local refNode = vm.compileNode(arg)
             if not refNode then
                 goto CONTINUE
             end
-            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()
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/hover/description.lua b/script/core/hover/description.lua
index bd584d746..58fb9fbe9 100644
--- a/script/core/hover/description.lua
+++ b/script/core/hover/description.lua
@@ -263,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
@@ -477,34 +477,55 @@ 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
 
 ---@async
@@ -512,6 +533,9 @@ return function (source)
     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
diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua
index d9fb2b6dd..ed596b3d5 100644
--- a/script/core/hover/init.lua
+++ b/script/core/hover/init.lua
@@ -86,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
@@ -137,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 6ce4dde9d..62e519272 100644
--- a/script/core/hover/label.lua
+++ b/script/core/hover/label.lua
@@ -134,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
@@ -143,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/reference.lua b/script/core/reference.lua
index f360a2f4d..4a0500b47 100644
--- a/script/core/reference.lua
+++ b/script/core/reference.lua
@@ -85,8 +85,11 @@ return function (uri, position, includeDeclaration)
         if src.type == 'self' then
             goto CONTINUE
         end
-        if not includeDeclaration and guide.isSet(src) then
-            goto CONTINUE
+        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'
diff --git a/script/core/rename.lua b/script/core/rename.lua
index 3d24b90cd..507def208 100644
--- a/script/core/rename.lua
+++ b/script/core/rename.lua
@@ -178,6 +178,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
 
@@ -234,7 +239,8 @@ local function ofDocTypeName(source, newname, callback)
     end
     local refs = vm.getRefs(source)
     for _, doc in ipairs(refs) do
-        if doc.type == 'doc.type.name' then
+        if doc.type == 'doc.type.name'
+        or doc.type == 'doc.extends.name' then
             callback(doc, doc.start, doc.finish, newname)
         end
     end
@@ -278,10 +284,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'
@@ -313,7 +322,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'
@@ -349,11 +360,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 = {}
diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua
index 7b9adad91..4e1d8e00a 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'
@@ -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
@@ -694,6 +703,22 @@ 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)
 
 ---@param state table
 ---@param results table
@@ -701,6 +726,7 @@ 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  = source.start
         local finishPos = source.finish
@@ -710,18 +736,23 @@ local function buildTokens(state, results)
         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,7 +817,12 @@ local function solveMultilineAndOverlapping(state, results)
     for token in tokens:pairs() do
         local startPos = converter.packPosition(state, token.start)
         local endPos   = converter.packPosition(state, token.finish)
-        if endPos.line == startPos.line then
+        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,
@@ -817,6 +853,7 @@ local function solveMultilineAndOverlapping(state, results)
                 }
             end
         end
+        ::continue::
     end
 
     return new
diff --git a/script/core/signature.lua b/script/core/signature.lua
index 3465fda2f..98018b215 100644
--- a/script/core/signature.lua
+++ b/script/core/signature.lua
@@ -89,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
@@ -134,12 +167,13 @@ 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' 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
@@ -149,7 +183,8 @@ local function makeSignatures(text, call, pos)
                 if set.type == 'doc.class' then
                     for _, overload in ipairs(set.calls) do
                         local f = overload.overload
-                        if not mark[f] then
+                        if  not mark[f]
+                        and not isEventNotMatch(call, src) then
                             mark[f] = true
                             signs[#signs+1] = makeOneSignature(f, oop, index)
                         end
diff --git a/script/core/workspace-symbol.lua b/script/core/workspace-symbol.lua
index f7becf933..a41cfc1dd 100644
--- a/script/core/workspace-symbol.lua
+++ b/script/core/workspace-symbol.lua
@@ -116,7 +116,7 @@ local function searchClassField(key, suri, results)
         return
     end
     suri = suri or guide.getUri(set)
-    vm.getClassFields(suri, global, nil, function (field, isMark)
+    vm.getClassFields(suri, global, vm.ANY, function (field)
         if field.type == 'generic' then
             return
         end
diff --git a/script/file-uri.lua b/script/file-uri.lua
index 3e916acf7..8a075f7ed 100644
--- a/script/file-uri.lua
+++ b/script/file-uri.lua
@@ -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
 
@@ -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 0154bb50a..490ae5048 100644
--- a/script/files.lua
+++ b/script/files.lua
@@ -19,18 +19,20 @@ 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 diffInfo?    table[]
----@field cache        table
----@field id           integer
+---@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
@@ -77,6 +79,9 @@ end
 ---@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)
@@ -212,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
@@ -253,14 +264,14 @@ function m.setText(uri, text, isTrust, callback)
     end
     local clock = os.clock()
     local newText = pluginOnSetText(file, text)
-    m.stateMap[uri] = nil
-    file.text       = newText
-    file.trusted    = isTrust
-    file.originText = text
-    file.rows       = nil
-    file.words      = 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
@@ -274,7 +285,6 @@ function m.setText(uri, text, isTrust, callback)
             util.saveFile(LOGPATH .. '/diffed.lua', newText)
         end
     end
-    m.getState(uri)
     log.trace('Set text:', uri, 'takes', os.clock() - clock, 'sec.')
 
     --if instance or TEST then
@@ -304,10 +314,10 @@ function m.setRawText(uri, text)
     if not text then
         return
     end
-    m.stateMap[uri] = nil
     local file = m.fileMap[uri]
     file.text             = text
     file.originText       = text
+    m.removeState(file)
 end
 
 function m.getCachedRows(uri)
@@ -460,8 +470,8 @@ function m.remove(uri)
     if not file then
         return
     end
+    m.removeState(file)
     m.fileMap[uri]        = nil
-    m.stateMap[uri]       = nil
     m._pairsCache         = nil
 
     m.fileCount     = m.fileCount - 1
@@ -559,6 +569,12 @@ function m.compileStateThen(state, file)
         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
 
@@ -692,7 +708,8 @@ end
 ---@class parser.state
 ---@field diffInfo? table[]
 ---@field originLines? integer[]
----@field originText string
+---@field originText? string
+---@field lua? string
 
 --- 获取文件语法树
 ---@param uri uri
@@ -703,7 +720,6 @@ function m.getState(uri)
         return nil
     end
     local state = m.compileState(uri)
-    file.cacheActiveTime = timer.clock()
     return state
 end
 
@@ -764,7 +780,6 @@ function m.getCache(uri)
     if not file then
         return nil
     end
-    --file.cacheActiveTime = timer.clock()
     return file.cache
 end
 
@@ -876,6 +891,41 @@ function m.countStates()
     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)
@@ -890,23 +940,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 85a500465..a8fa925f7 100644
--- a/script/filewatch.lua
+++ b/script/filewatch.lua
@@ -2,6 +2,7 @@ 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
@@ -32,16 +33,28 @@ local m = {}
 m._eventList = {}
 m._watchings = {}
 
-function m.watch(path)
-    if path == '' then
+---@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
@@ -53,7 +66,6 @@ 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
@@ -75,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/gc.lua b/script/gc.lua
index 7bb81569c..ff22195e9 100644
--- a/script/gc.lua
+++ b/script/gc.lua
@@ -27,7 +27,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 9065060ad..aedcc38c6 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
@@ -54,6 +58,7 @@ CHECKLEVEL = 'Warning'
 ---@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
 
@@ -64,3 +69,14 @@ 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
diff --git a/script/json-beautify.lua b/script/json-beautify.lua
index f86a7625b..f3467f078 100644
--- a/script/json-beautify.lua
+++ b/script/json-beautify.lua
@@ -79,11 +79,13 @@ function encode_map.table(t)
         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])
+        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]
             statusBuilder[#statusBuilder+1] = ","
@@ -154,16 +156,21 @@ local function beautify_option(option)
     return setmetatable(option or {}, defaultOpt)
 end
 
-local function beautify(v, option)
+local function beautify_builder(builder, v, option)
     statusVisited = {}
-    statusBuilder = {}
+    statusBuilder = builder
     statusOpt = beautify_option(option)
     statusDep = statusOpt.depth
     encode(v)
+end
+
+local function beautify(v, option)
+    beautify_builder({}, v, option)
     return table_concat(statusBuilder)
 end
 
 json.beautify = beautify
-json.beautify_option = beautify_option
+json._beautify_builder = beautify_builder
+json._beautify_option = beautify_option
 
 return json
diff --git a/script/json-edit.lua b/script/json-edit.lua
index 7f20b523d..30a552508 100644
--- a/script/json-edit.lua
+++ b/script/json-edit.lua
@@ -1,9 +1,9 @@
----@diagnostic disable: param-type-mismatch
-
 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
@@ -45,6 +45,12 @@ if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then
         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
@@ -376,6 +382,8 @@ local function decode_item()
     end
 end
 
+local JsonEmpty = function () end
+
 local function decode_ast(str)
     if type(str) ~= "string" then
         error("expected argument of type string, got " .. type(str))
@@ -384,7 +392,7 @@ local function decode_ast(str)
     statusPos = 1
     statusTop = 0
     if next_byte() == -1 then
-        return json.null
+        return {s = statusPos, d = statusTop, f = statusPos, v = JsonEmpty}
     end
     local res = decode()
     while statusTop > 0 do
@@ -407,7 +415,7 @@ end
 local function query_(ast, pathlst, n)
     local data = ast.v
     if type(data) ~= "table" then
-        return
+        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)
@@ -416,28 +424,36 @@ local function query_(ast, pathlst, n)
             k = #data + 1
         else
             if k:match "^0%d+" then
-                return
+                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
+                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
-    return query_(data[k], pathlst, n + 1)
+    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 query(ast, path)
+local function split_path(path)
     if type(path) ~= "string" then
-        return
+        return nil, "path is not a string"
     end
     if path:sub(1,1) ~= "/" then
-        return
+        return nil, "path must start with `/`"
     end
-    return query_(ast, split(path:sub(2)), 1)
+    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)
@@ -475,50 +491,59 @@ local function find_max_node(t)
     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
-    return start_text
-        .. json.beautify(value, option)
-        .. ","
-        .. option.newline
-        .. string_rep(option.indent, option.depth)
-        .. finish_text
+    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
-    return start_text
-        .. ","
-        .. option.newline
-        .. string_rep(option.indent, option.depth)
-        .. json.beautify(value, option)
-        .. finish_text
+    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
-    return start_text
-        .. option.newline
-        .. string_rep(option.indent, option.depth)
-        .. json.beautify(value, option)
-        .. option.newline
-        .. string_rep(option.indent, option.depth-1)
-        .. finish_text
+    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
-    return start_text
-        .. json.beautify(value, option)
-        .. finish_text
+    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)
@@ -527,29 +552,31 @@ local function apply_object_insert(str, option, value, t, k)
         local start_text = str:sub(1, node.f-1)
         local finish_text = str:sub(node.f)
         option.depth = option.depth + node.d
-        return start_text
-            .. ","
-            .. option.newline
-            .. string_rep(option.indent, option.depth)
-            .. '"'
-            .. json._encode_string(k)
-            .. '": '
-            .. json.beautify(value, option)
-            .. finish_text
+        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
-        return start_text
-            .. option.newline
-            .. string_rep(option.indent, option.depth)
-            .. '"'
-            .. json._encode_string(k)
-            .. '": '
-            .. json.beautify(value, option)
-            .. option.newline
-            .. string_rep(option.indent, option.depth-1)
-            .. finish_text
+        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
 
@@ -565,20 +592,33 @@ local function apply_remove(str, s, f)
     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 value == nil then
-        return
-    end
-    if path == '' then
+    if path == '/' then
         return json.beautify(value, option)
     end
     local ast = decode_ast(str)
-    local t, k, isarray = query(ast, path)
+    if ast.v == JsonEmpty then
+        local pathlst = split_path(path)
+        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])
@@ -597,39 +637,55 @@ function OP.add(str, option, path, value)
 end
 
 function OP.remove(str, _, path)
-    if path == '' then
+    if path == '/' then
         return ''
     end
     local ast = decode_ast(str)
-    local t, k, isarray = query(ast, path)
+    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
-            return
+            --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
-            return
+            --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 value == nil then
-        return
-    end
-    if path == '' then
+    if path == '/' then
         return json.beautify(value, option)
     end
     local ast = decode_ast(str)
-    local t, k, isarray = query(ast, path)
+    if ast.v == JsonEmpty then
+        local pathlst = split_path(path)
+        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
@@ -648,9 +704,10 @@ 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)
+    option = json._beautify_option(option)
     return f(str, option, patch.path, patch.value)
 end
 
diff --git a/script/json.lua b/script/json.lua
index 899d8c367..eaccb43e0 100644
--- a/script/json.lua
+++ b/script/json.lua
@@ -183,11 +183,13 @@ function encode_map.table(t)
             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] = ',"'
@@ -219,6 +221,10 @@ function encode_map.table(t)
         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
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/library.lua b/script/library.lua
index da4d77d51..3a9bbbc6c 100644
--- a/script/library.lua
+++ b/script/library.lua
@@ -188,10 +188,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)
@@ -222,11 +226,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)
@@ -241,13 +242,10 @@ 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
 
         ---@type fs.path
         local libPath = templateDir / (libName .. '.lua')
-        local metaDoc = compileSingleMetaDoc(uri, fsu.loadFile(libPath), metaLang, status)
+        local metaDoc, include = compileSingleMetaDoc(uri, fsu.loadFile(libPath), metaLang, status)
         if metaDoc then
             metaDoc = encoder.encode(encoding, metaDoc, 'auto')
 
@@ -258,13 +256,17 @@ local function initBuiltIn(uri)
 
             local ok, err = out:saveFile(outputLibName, metaDoc)
             if not ok then
-                log.debug("Save Meta File:", err)
+                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
@@ -541,6 +543,7 @@ local function check3rdByWords(uri, configs)
             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)
@@ -576,6 +579,7 @@ local function check3rdByFileName(uri, configs)
             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)
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 6190dac78..c4267b976 100644
--- a/script/meta/bee/filesystem.lua
+++ b/script/meta/bee/filesystem.lua
@@ -31,11 +31,11 @@ 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
@@ -64,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
 
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 b8040382c..5321d9b87 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,7 +233,7 @@ 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
 
@@ -431,6 +432,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 +470,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)
@@ -715,7 +729,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 +1380,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 +1423,7 @@ local function parseName(asAction)
             finish = finishPos,
         }
     end
-    if isKeyWord(word) then
+    if isKeyWord(word, Tokens[Index + 1]) then
         pushError {
             type   = 'KEYWORD',
             start  = startPos,
@@ -1477,7 +1504,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'
@@ -1947,6 +1974,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
@@ -2209,7 +2242,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'),
@@ -2232,14 +2265,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
@@ -2262,6 +2294,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 {
@@ -2274,11 +2307,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,
@@ -2302,6 +2342,7 @@ local function parseFunction(isLocal, isAction)
         if Tokens[Index + 1] == ')' then
             local parenRight = getPosition(Tokens[Index], 'right')
             func.finish = parenRight
+            func.bstart = parenRight
             if params then
                 params.finish = parenRight
             end
@@ -2309,6 +2350,7 @@ local function parseFunction(isLocal, isAction)
             skipSpace(true)
         else
             func.finish = lastRightPosition()
+            func.bstart = func.finish
             if params then
                 params.finish = func.finish
             end
@@ -2334,7 +2376,16 @@ local function parseFunction(isLocal, isAction)
     return func
 end
 
-local function pushErrorNeedParen(source)
+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,
@@ -2353,6 +2404,7 @@ local function pushErrorNeedParen(source)
             }
         }
     }
+    return exp
 end
 
 local function parseExpUnit()
@@ -2372,10 +2424,7 @@ local function parseExpUnit()
         if not table then
             return nil
         end
-        local exp = parseSimple(table, false)
-        if exp ~= table then
-            pushErrorNeedParen(table)
-        end
+        local exp = checkNeedParen(table)
         return exp
     end
 
@@ -2384,10 +2433,7 @@ local function parseExpUnit()
         if not string then
             return nil
         end
-        local exp = parseSimple(string, false)
-        if exp ~= string then
-            pushErrorNeedParen(string)
-        end
+        local exp = checkNeedParen(string)
         return exp
     end
 
@@ -2396,10 +2442,7 @@ local function parseExpUnit()
         if not string then
             return nil
         end
-        local exp = parseSimple(string, false)
-        if exp ~= string then
-            pushErrorNeedParen(string)
-        end
+        local exp = checkNeedParen(string)
         return exp
     end
 
@@ -2852,6 +2895,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
@@ -2866,14 +2911,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
@@ -2963,6 +3008,7 @@ local function parseDo()
         type   = 'do',
         start  = doLeft,
         finish = doRight,
+        bstart = doRight,
         keyword = {
             [1] = doLeft,
             [2] = doRight,
@@ -3145,6 +3191,7 @@ local function parseIfBlock(parent)
         parent  = parent,
         start   = ifLeft,
         finish  = ifRight,
+        bstart  = ifRight,
         keyword = {
             [1] = ifLeft,
             [2] = ifRight,
@@ -3155,7 +3202,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
@@ -3164,6 +3212,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
@@ -3203,6 +3252,7 @@ local function parseElseIfBlock(parent)
         parent  = parent,
         start   = ifLeft,
         finish  = ifRight,
+        bstart  = ifRight,
         keyword = {
             [1] = ifLeft,
             [2] = ifRight,
@@ -3214,6 +3264,7 @@ local function parseElseIfBlock(parent)
     if filter then
         elseifblock.filter = filter
         elseifblock.finish = filter.finish
+        elseifblock.bstart = elseifblock.finish
         filter.parent = elseifblock
     else
         missExp()
@@ -3223,6 +3274,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
@@ -3262,6 +3314,7 @@ local function parseElseBlock(parent)
         parent  = parent,
         start   = ifLeft,
         finish  = ifRight,
+        bstart  = ifRight,
         keyword = {
             [1] = ifLeft,
             [2] = ifRight,
@@ -3337,6 +3390,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
@@ -3348,6 +3402,7 @@ local function parseFor()
         missName()
     end
     skipSpace()
+    local forStateVars
     -- for i =
     if expectAssign() then
         action.type = 'loop'
@@ -3362,10 +3417,14 @@ local function parseFor()
                 name = nameOrList[1]
             end
         end
+        -- for x in ... uses 4 variables
+        forStateVars = 3
+        LocalCount = LocalCount + forStateVars
         if name then
             local loc = createLocal(name)
             loc.parent    = action
             action.finish = name.finish
+            action.bstart = action.finish
             action.loc    = loc
         end
         if expList then
@@ -3375,12 +3434,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',
@@ -3393,6 +3454,7 @@ local function parseFor()
                 step.parent   = expList
                 action.step   = step
                 action.finish = step.finish
+                action.bstart = action.finish
             end
         else
             pushError {
@@ -3414,7 +3476,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
 
@@ -3435,6 +3498,7 @@ local function parseFor()
             local lastExp = exps[#exps]
             if lastExp then
                 action.finish = lastExp.finish
+                action.bstart = action.finish
             end
 
             action.exps = exps
@@ -3447,6 +3511,13 @@ 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
@@ -3468,6 +3539,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
@@ -3507,6 +3579,9 @@ local function parseFor()
     if action.locals then
         LocalCount = LocalCount - #action.locals
     end
+    if forStateVars then
+        LocalCount = LocalCount - forStateVars
+    end
 
     return action
 end
@@ -3518,6 +3593,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
@@ -3542,6 +3618,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
@@ -3594,6 +3671,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
@@ -3731,7 +3809,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
 
@@ -3831,6 +3909,7 @@ local function initState(lua, version, options)
     LineOffset          = 1
     LastTokenFinish     = 0
     LocalCount          = 0
+    LocalLimited        = false
     Chunk               = {}
     Tokens              = tokens(lua)
     Index               = 1
diff --git a/script/parser/guide.lua b/script/parser/guide.lua
index 147e62378..0dd6338e4 100644
--- a/script/parser/guide.lua
+++ b/script/parser/guide.lua
@@ -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,11 @@ local type         = type
 ---@field hasGoTo?              true
 ---@field hasReturn?            true
 ---@field hasBreak?             true
----@field hasError?             true
+---@field hasExit?              true
 ---@field [integer]             parser.object|any
----@field package _root          parser.object
+---@field package _root         parser.object
+---@field package _eachCache?   parser.object[]
+---@field package _isGlobal?    boolean
 
 ---@class guide
 ---@field debugMode boolean
@@ -81,7 +84,8 @@ local m = {}
 
 m.ANY = {"<ANY>"}
 
-m.namePattern = '[%a_\x80-\xff][%w_\x80-\xff]*'
+m.notNamePattern  = '[^%w_\x80-\xff]'
+m.namePattern     = '[%a_\x80-\xff][%w_\x80-\xff]*'
 m.namePatternFull = '^' .. m.namePattern .. '$'
 
 local blockTypes = {
@@ -98,6 +102,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,
@@ -112,40 +126,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'},
@@ -164,9 +178,11 @@ local childMap = {
     ['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.meta']           = {'name'},
+    ['doc.attr']           = {'#names'},
 }
 
 ---@type table<string, fun(obj: parser.object, list: parser.object[])>
@@ -432,7 +448,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
@@ -466,6 +482,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
@@ -704,7 +723,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)
@@ -883,7 +902,7 @@ function m.getLineRange(state, row)
     return 0
 end
 
-local isSetMap = {
+local assignTypeMap = {
     ['setglobal']         = true,
     ['local']             = true,
     ['self']              = true,
@@ -893,7 +912,6 @@ local isSetMap = {
     ['setindex']          = true,
     ['tablefield']        = true,
     ['tableindex']        = true,
-    ['tableexp']          = true,
     ['label']             = true,
     ['doc.class']         = true,
     ['doc.alias']         = true,
@@ -906,9 +924,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
@@ -920,7 +938,7 @@ function m.isSet(source)
     return false
 end
 
-local isGetMap = {
+local getTypeMap = {
     ['getglobal'] = true,
     ['getlocal']  = true,
     ['getfield']  = true,
@@ -929,7 +947,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
@@ -1013,7 +1031,8 @@ function m.getKeyName(obj)
     or     tp == 'doc.type.name'
     or     tp == 'doc.class.name'
     or     tp == 'doc.alias.name'
-    or     tp == 'doc.enum.name' then
+    or     tp == 'doc.enum.name'
+    or     tp == 'doc.extends.name' then
         return obj[1]
     elseif tp == 'doc.type.field' then
         return m.getKeyName(obj.name)
@@ -1086,99 +1105,6 @@ function m.getKeyType(obj)
     return m.getKeyTypeOfLiteral(obj)
 end
 
---- 测试 a 到 b 的路径（不经过函数，不考虑 goto），
---- 每个路径是一个 block 。
----
---- 如果 a 在 b 的前面，返回 `"before"` 加上 2个`list<block>`
----
---- 如果 a 在 b 的后面，返回 `"after"` 加上 2个`list<block>`
----
---- 否则返回 `false`
----
---- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。
----@param a table
----@param b table
----@return string|false mode
----@return table? pathA
----@return table? pathB
-function m.getPath(a, b, sameFunction)
-    --- 首先测试双方在同一个函数内
-    if sameFunction and m.getParentFunction(a) ~= m.getParentFunction(b) then
-        return false
-    end
-    local mode
-    local objA
-    local objB
-    if a.finish < b.start then
-        mode = 'before'
-        objA = a
-        objB = b
-    elseif a.start > b.finish then
-        mode = 'after'
-        objA = b
-        objB = a
-    else
-        return 'equal', {}, {}
-    end
-    local pathA = {}
-    local pathB = {}
-    for _ = 1, 1000 do
-        objA = m.getParentBlock(objA)
-        if objA then
-            pathA[#pathA+1] = objA
-            if (not sameFunction and objA.type == 'function') or objA.type == 'main' then
-                break
-            end
-        end
-    end
-    for _ = 1, 1000 do
-        objB = m.getParentBlock(objB)
-        if objB then
-            pathB[#pathB+1] = objB
-            if (not sameFunction and objB.type == 'function') or objB.type == 'main' then
-                break
-            end
-        end
-    end
-    -- pathA: {1, 2, 3, 4, 5}
-    -- pathB: {5, 6, 2, 3}
-    local top = #pathB
-    local start
-    for i = #pathA, 1, -1 do
-        local currentBlock = pathA[i]
-        if currentBlock == pathB[top] then
-            start = i
-            break
-        end
-    end
-    if not start then
-        return false
-    end
-    -- pathA: {   1, 2, 3}
-    -- pathB: {5, 6, 2, 3}
-    local extra = 0
-    local align = top - start
-    for i = start, 1, -1 do
-        local currentA = pathA[i]
-        local currentB = pathB[i+align]
-        if currentA ~= currentB then
-            extra = i
-            break
-        end
-    end
-    -- pathA: {1}
-    local resultA = {}
-    for i = extra, 1, -1 do
-        resultA[#resultA+1] = pathA[i]
-    end
-    -- pathB: {5, 6}
-    local resultB = {}
-    for i = extra + align, 1, -1 do
-        resultB[#resultB+1] = pathB[i]
-    end
-    return mode, resultA, resultB
-end
-
 ---是否是全局变量（包括 _G.XXX 形式）
 ---@param source parser.object
 ---@return boolean
@@ -1337,4 +1263,33 @@ function m.getFunctionSelfNode(func)
     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
+
 return m
diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua
index 8e94f24d4..d7338918f 100644
--- a/script/parser/luadoc.lua
+++ b/script/parser/luadoc.lua
@@ -156,6 +156,7 @@ Symbol              <-  ({} {
 ---@field calls?            parser.object[]
 ---@field generics?         parser.object[]
 ---@field generic?          parser.object
+---@field docAttr?          parser.object
 
 local function parseTokens(text, offset)
     Ci = 0
@@ -252,6 +253,40 @@ local function nextSymbolOrError(symbol)
     return false
 end
 
+local function parseDocAttr(parent)
+    if not checkToken('symbol', '(', 1) then
+        return nil
+    end
+    nextToken()
+
+    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
@@ -700,6 +735,8 @@ local function parseResume(parent)
     return result
 end
 
+local lockResume = false
+
 function parseType(parent)
     local result = {
         type    = 'doc.type',
@@ -778,20 +815,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
@@ -814,6 +841,7 @@ local docSwitch = util.switch()
             operators = {},
             calls     = {},
         }
+        result.docAttr = parseDocAttr(result)
         result.class = parseName('doc.class.name', result)
         if not result.class then
             pushWarning {
@@ -1116,11 +1144,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 'version'
     : call(function ()
@@ -1316,7 +1346,7 @@ local docSwitch = util.switch()
             return result
         end
 
-        result.loc    = loc
+        result.name   = loc
         result.finish = loc.finish
 
         while true do
@@ -1434,17 +1464,22 @@ 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'
@@ -1499,21 +1534,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]
         end
     end
-    return comment
+    return util.trim(comment)
 end
 
 local function buildLuaDoc(comment)
@@ -1539,7 +1575,7 @@ local function buildLuaDoc(comment)
     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
@@ -1674,7 +1710,9 @@ local function bindDocWithSource(doc, source)
     if not source.bindDocs then
         source.bindDocs = {}
     end
-    source.bindDocs[#source.bindDocs+1] = doc
+    if source.bindDocs[#source.bindDocs] ~= doc then
+        source.bindDocs[#source.bindDocs+1] = doc
+    end
     doc.bindSource = source
 end
 
@@ -1825,7 +1863,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
@@ -1937,7 +1975,7 @@ local bindDocAccept = {
     'local'     , 'setlocal'  , 'setglobal',
     'setfield'  , 'setmethod' , 'setindex' ,
     'tablefield', 'tableindex', 'self'     ,
-    'function'  , 'table'     , '...'      ,
+    'function'  , 'return'     , '...'      ,
 }
 
 local function bindDocs(state)
@@ -2048,6 +2086,7 @@ return function (state)
         if not comment then
             break
         end
+        lockResume = false
         local doc, rests = buildLuaDoc(comment)
         if doc then
             insertDoc(doc, comment)
diff --git a/script/plugin.lua b/script/plugin.lua
index 870b68b6c..fc59bbade 100644
--- a/script/plugin.lua
+++ b/script/plugin.lua
@@ -128,6 +128,7 @@ end
 
 ws.watch(function (ev, uri)
     if ev == 'startReload' then
+        require 'plugins'
         initPlugin(uri)
     end
 end)
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 <const> = {
+                ["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 <const>      = '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 <const>   = {
+    ['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> = {
+    const = true,
+    restrict = true,
+    volatile = true,
+}
+
+local constName <const>      = '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/proto/converter.lua b/script/proto/converter.lua
index d761042cb..e86e4904a 100644
--- a/script/proto/converter.lua
+++ b/script/proto/converter.lua
@@ -4,6 +4,7 @@ local encoder = require 'encoder'
 
 local offsetEncoding = 'utf16'
 
+---@class converter
 local m = {}
 
 ---@alias position {line: integer, character: integer}
@@ -206,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 1bc1000d3..77e7a77dd 100644
--- a/script/proto/define.lua
+++ b/script/proto/define.lua
@@ -166,24 +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',
-    ['string.buffer']='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 fac07d985..faf870b9d 100644
--- a/script/proto/diagnostic.lua
+++ b/script/proto/diagnostic.lua
@@ -43,25 +43,9 @@ m.register {
 } {
     group    = 'luacheck',
     severity = 'Hint',
-    status   = 'Any',
+    status   = 'Opened',
 }
 
--- m.register {
---     'unused-local',
---     'unused-function',
---     'unused-label',
---     'unused-vararg',
---     'trailing-space',
---     'redundant-return',
---     'empty-block',
---     'code-after-break',
---     'unreachable-code',
--- } {
---     group    = 'unused',
---     severity = 'Hint',
---     status   = 'Opened',
--- }
-
 -- m.register {
 --     'redundant-value',
 --     'unbalanced-assignments',
@@ -70,6 +54,7 @@ m.register {
 --     'missing-return-value',
 --     'redundant-return-value',
 --     'missing-return',
+--     'missing-fields',
 -- } {
 --     group    = 'unbalanced',
 --     severity = 'Warning',
@@ -84,6 +69,7 @@ m.register {
 --     'param-type-mismatch',
 --     'cast-type-mismatch',
 --     'return-type-mismatch',
+--     'inject-field',
 -- } {
 --     group    = 'type-check',
 --     severity = 'Warning',
@@ -108,6 +94,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'
 -- } {
@@ -124,6 +120,14 @@ m.register {
 --     status   = 'None',
 -- }
 
+-- m.register {
+--     'name-style-check'
+-- } {
+--     group    = 'codestyle',
+--     severity = 'Warning',
+--     status   = 'None',
+-- }
+
 -- m.register {
 --     'newline-call',
 --     'newfield-call',
@@ -179,6 +183,14 @@ m.register {
 --     status   = 'Any',
 -- }
 
+-- m.register {
+--     'global-element',
+-- } {
+--     group   = 'conventions',
+--     severity = 'Warning',
+--     status = 'None'
+-- }
+
 -- m.register {
 --     'duplicate-index',
 -- } {
diff --git a/script/proto/proto.lua b/script/proto/proto.lua
index 024b17e50..73544ffcc 100644
--- a/script/proto/proto.lua
+++ b/script/proto/proto.lua
@@ -1,4 +1,5 @@
 local subprocess = require 'bee.subprocess'
+local socket     = require 'bee.socket'
 local util       = require 'utility'
 local await      = require 'await'
 local pub        = require 'pub'
@@ -6,6 +7,7 @@ local jsonrpc    = require 'jsonrpc'
 local define     = require 'proto.define'
 local json       = require 'json'
 local inspect    = require 'inspect'
+local thread     = require 'bee.thread'
 
 local reqCounter = util.counter()
 
@@ -23,11 +25,15 @@ 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'
+---@type bee.socket.fd
+m.fd      = nil
 
 function m.getMethodName(proto)
     if proto.method:sub(1, 2) == '$/' then
@@ -45,7 +51,11 @@ 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.fd:send(buf)
+    end
 end
 
 function m.response(id, res)
@@ -53,7 +63,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 +79,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 +197,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 +228,24 @@ 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
+        subprocess.filemode(io.stdin,  'b')
+        subprocess.filemode(io.stdout, 'b')
+        io.stdin:setvbuf  'no'
+        io.stdout:setvbuf 'no'
+        pub.task('loadProtoByStdio')
+    elseif mode == 'socket' then
+        local rfd = assert(socket('tcp'))
+        rfd:connect('127.0.0.1', socketPort)
+        local wfd1, wfd2 = socket.pair()
+        m.fd = wfd1
+        pub.task('loadProtoBySocket', {
+            wfd = wfd2:detach(),
+            rfd = rfd:detach(),
+        })
+    end
 end
 
 return m
diff --git a/script/provider/capability.lua b/script/provider/capability.lua
index 19024a54e..4f7ec3885 100644
--- a/script/provider/capability.lua
+++ b/script/provider/capability.lua
@@ -7,6 +7,7 @@ local define     = require 'proto.define'
 require 'provider.semantic-tokens'
 require 'provider.formatting'
 require 'provider.inlay-hint'
+require 'provider.code-lens'
 
 local 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 41e100101..d255c191e 100644
--- a/script/provider/diagnostic.lua
+++ b/script/provider/diagnostic.lua
@@ -638,6 +638,12 @@ 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
diff --git a/script/provider/formatting.lua b/script/provider/formatting.lua
index 4ec5545a2..58ac6f81e 100644
--- a/script/provider/formatting.lua
+++ b/script/provider/formatting.lua
@@ -82,16 +82,8 @@ function m.updateNonStandardSymbols(symbols)
         return
     end
 
-    local eqTokens = {}
-    for _, token in ipairs(symbols) do
-        if token:find("=") and token ~= "!=" then
-            table.insert(eqTokens, token)
-        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 e61d6a0d1..1b9773a11 100644
--- a/script/provider/provider.lua
+++ b/script/provider/provider.lua
@@ -36,7 +36,7 @@ function m.updateConfig(uri)
         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
 
@@ -114,10 +114,10 @@ m.register 'initialize' {
 
         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
 
         local response = {
@@ -134,7 +134,6 @@ m.register 'initialize' {
 m.register 'initialized'{
     ---@async
     function (params)
-        files.init()
         local _ <close> = progress.create(workspace.getFirstScope().uri, lang.script.WINDOW_INITIALIZING, 0.5)
         m.updateConfig()
         local registrations = {}
@@ -250,31 +249,31 @@ m.register 'workspace/didChangeWorkspaceFolders' {
     function (params)
         log.debug('workspace/didChangeWorkspaceFolders', inspect(params))
         for _, folder in ipairs(params.event.added) do
-            workspace.create(folder.uri)
+            local uri = files.getRealUri(folder.uri)
+            workspace.create(uri)
             m.updateConfig()
-            workspace.reload(scope.getScope(folder.uri))
+            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
 }
 
 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)
         local text  = doc.text
         files.setText(uri, text, true, function (file)
             file.version = doc.version
         end)
         files.open(uri)
+        workspace.awaitReady(uri)
+        files.compileState(uri)
     end
 }
 
@@ -293,17 +292,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)
@@ -602,19 +597,7 @@ 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 _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_COMPLETION, 0.5)
         --log.info(util.dump(params))
@@ -729,11 +712,11 @@ m.register 'completionItem/resolve' {
         --await.setPriority(1000)
         local state = files.getState(uri)
         if not state then
-            return nil
+            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 {
@@ -789,8 +772,8 @@ 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
@@ -915,6 +898,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 = {
@@ -923,6 +953,7 @@ m.register 'workspace/executeCommand' {
                 'lua.solve',
                 'lua.jsonToLua',
                 'lua.setConfig',
+                'lua.getConfig',
                 'lua.autoRequire',
             },
         },
@@ -941,10 +972,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
 }
@@ -1374,8 +1416,8 @@ m.register 'textDocument/inlayHint' {
                 },
                 position     = converter.packPosition(state, res.offset),
                 kind         = res.kind,
-                paddingLeft  = true,
-                paddingRight = true,
+                paddingLeft  = res.kind == 1,
+                paddingRight = res.kind == 2,
             }
         end
         return hintResults
diff --git a/script/service/init.lua b/script/service/init.lua
index 09204c848..eb0bd057c 100644
--- a/script/service/init.lua
+++ b/script/service/init.lua
@@ -1,4 +1,3 @@
-require 'service.telemetry'
 local service = require 'service.service'
 
 return service
diff --git a/script/service/service.lua b/script/service/service.lua
index cd83dd544..7011ec4f7 100644
--- a/script/service/service.lua
+++ b/script/service/service.lua
@@ -267,7 +267,12 @@ function m.start()
     if COMPILECORES and COMPILECORES > 0 then
         pub.recruitBraves(COMPILECORES, 'compile')
     end
-    proto.listen()
+    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()
diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua
deleted file mode 100644
index 211dff0e7..000000000
--- a/script/service/telemetry.lua
+++ /dev/null
@@ -1,203 +0,0 @@
-local net      = require 'service.net'
-local timer    = require 'timer'
-local config   = require 'config'
-local client   = require 'client'
-local nonil    = require 'without-check-nil'
-local util     = require 'utility'
-local platform = require 'bee.platform'
-local proto    = require 'proto.proto'
-local lang     = require 'language'
-local define   = require 'proto.define'
-local await    = require 'await'
-local version  = require 'version'
-local ws       = require 'workspace'
-
-local tokenPath = (ROOT / 'log' / 'token'):string()
-local token = util.loadFile(tokenPath)
-if not token then
-    token = ('%016X'):format(math.random(0, math.maxinteger))
-    util.saveFile(tokenPath, token)
-end
-
-log.debug('Telemetry Token:', token)
-
-local function getClientName()
-    nonil.enable()
-    local clientName    = client.info.clientInfo.name
-    local clientVersion = client.info.clientInfo.version
-    nonil.disable()
-    return table.concat({clientName, clientVersion}, ' ')
-end
-
-local function send(link, msg)
-    link:write(('s4'):pack(msg))
-end
-
-local function pushClientInfo(link)
-    send(link, string.pack('zzz'
-        , 'pulse'
-        , token
-        , getClientName()
-    ))
-end
-
-local function pushPlatformInfo(link)
-    send(link, string.pack('zzzzz'
-        , 'platform'
-        , token
-        , ('%s %s'):format(platform.OS, platform.Arch)
-        , ('%s %s'):format(platform.CRT, platform.CRTVersion)
-        , ('%s %s'):format(platform.Compiler, platform.CompilerVersion)
-    ))
-end
-
-local function pushVersion(link)
-    send(link, string.pack('zzz'
-        , 'version'
-        , token
-        , version.getVersion()
-    ))
-end
-
-local function occlusionPath(str)
-    return str:gsub('(%s*)([^:"\'%<%[%]\r\n]+)', function (left, chunk)
-        if not chunk:find '[/\\]' then
-            return
-        end
-        local newStr, count = chunk:gsub('.+([/\\]script[/\\])', '***%1')
-        if count > 0 then
-            return left .. newStr
-        elseif chunk:sub(1, 1) == '\\'
-        or     chunk:sub(1, 1) == '/'
-        or     chunk:sub(1, 3) == '...' then
-            return left .. '***'
-        end
-    end)
-end
-
-local function pushErrorLog(link)
-    local err = log.firstError
-    if not err then
-        return
-    end
-    log.firstError = nil
-    err = occlusionPath(err)
-    send(link, string.pack('zzzz'
-        , 'error'
-        , token
-        , getClientName()
-        , ('%q'):format(err)
-    ))
-end
-
----@type boolean?
-local isValid  = false
-
-timer.wait(5, function ()
-    timer.loop(300, function ()
-        if isValid ~= true then
-            return
-        end
-        local suc, link = pcall(net.connect, 'tcp', 'moe-moe.love', 11577)
-        if not suc then
-            suc, link = pcall(net.connect, 'tcp', '154.23.191.39', 11577)
-        end
-        if not suc or not link then
-            return
-        end
-        function link:on_connect()
-            pushClientInfo(link)
-            pushPlatformInfo(link)
-            pushVersion(link)
-            pushErrorLog(link)
-            self:close()
-        end
-    end)()
-    timer.loop(1, function ()
-        if isValid ~= true then
-            return
-        end
-        net.update()
-    end)
-end)
-
-local m = {}
-
-function m.updateConfig()
-    local enable = config.get(nil, 'Lua.telemetry.enable')
-    assert(enable == true or enable == false or enable == nil)
-    isValid = enable
-    if isValid == false then
-        return
-    end
-    for _, scp in ipairs(ws.folders) do
-        if config.get(scp.uri, 'Lua.telemetry.enable') == false then
-            isValid = false
-            return
-        end
-    end
-    for _, scp in ipairs(ws.folders) do
-        if config.get(scp.uri, 'Lua.telemetry.enable') == true then
-            isValid = true
-            break
-        end
-    end
-    if isValid ~= nil then
-        return
-    end
-    if not client.getOption 'changeConfiguration' then
-        return
-    end
-    if m.hasShowedMessage then
-        return
-    end
-    m.hasShowedMessage = true
-
-    await.call(function () ---@async
-        local enableTitle  = lang.script.WINDOW_TELEMETRY_ENABLE
-        local disableTitle = lang.script.WINDOW_TELEMETRY_DISABLE
-        local item = proto.awaitRequest('window/showMessageRequest', {
-            message = lang.script.WINDOW_TELEMETRY_HINT,
-            type    = define.MessageType.Info,
-            actions = {
-                {
-                    title = enableTitle,
-                },
-                {
-                    title = disableTitle,
-                },
-            }
-        })
-        if not item then
-            return
-        end
-        if item.title == enableTitle then
-            client.setConfig {
-                {
-                    key      = 'Lua.telemetry.enable',
-                    action   = 'set',
-                    value    = true,
-                    global   = true,
-                }
-            }
-        elseif item.title == disableTitle then
-            client.setConfig {
-                {
-                    key      = 'Lua.telemetry.enable',
-                    action   = 'set',
-                    value    = false,
-                    global   = true,
-                }
-            }
-        end
-    end)
-end
-
-config.watch(function (uri, key, value)
-    if key == 'Lua.telemetry.enable'
-    or key == '' then
-        m.updateConfig()
-    end
-end)
-
-return m
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/utility.lua b/script/utility.lua
index 55965cf29..b383a1714 100644
--- a/script/utility.lua
+++ b/script/utility.lua
@@ -283,6 +283,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 +311,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 +341,10 @@ function m.counter(init, step)
 end
 
 --- 排序后遍历
----@param t table
+---@generic K, V
+---@param t table<K, V>
+---@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 +362,7 @@ end
 --- 深拷贝（不处理元表）
 ---@param source  table
 ---@param target? table
+---@return table
 function m.deepCopy(source, target)
     local mark = {}
     local function copy(a, b)
@@ -376,6 +385,8 @@ function m.deepCopy(source, target)
 end
 
 --- 序列化
+---@param t table
+---@return table
 function m.unpack(t)
     local result = {}
     local tid = 0
@@ -403,6 +414,8 @@ function m.unpack(t)
 end
 
 --- 反序列化
+---@param t table
+---@return table
 function m.pack(t)
     local cache = {}
     local function pack(id)
@@ -515,18 +528,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<K, V>
+---@return table<V, K>
 function m.revertMap(t)
     local nt = {}
     for k, v in pairs(t) do
@@ -621,6 +641,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 +673,16 @@ function m.sortByScore(tbl, callbacks)
     end)
 end
 
+---@param arr any[]
+---@return SortByScoreCallback
+function m.sortCallbackOfIndex(arr)
+    ---@type table<any, integer>
+    local indexMap = m.revertMap(arr)
+    return function (v)
+        return - indexMap[v]
+    end
+end
+
 ---裁剪字符串
 ---@param str string
 ---@param mode? '"left"'|'"right"'
@@ -730,6 +765,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 +785,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)
@@ -816,6 +854,7 @@ end
 
 ---@param t table
 ---@param sorter boolean|function
+---@return any[]
 function m.getTableKeys(t, sorter)
     local keys = {}
     for k in pairs(t) do
@@ -838,6 +877,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
@@ -870,4 +918,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 168ad536b..7e0264740 100644
--- a/script/vm/compiler.lua
+++ b/script/vm/compiler.lua
@@ -7,16 +7,19 @@ local files      = require 'files'
 local vm         = require 'vm.vm'
 
 ---@class parser.object
----@field _compiledNodes     boolean
----@field _node              vm.node
----@field package _globalBase table
----@field cindex             integer
----@field func               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
@@ -29,6 +32,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
@@ -56,19 +65,37 @@ local function bindDocs(source)
             vm.setNode(source, vm.compileNode(ast))
             return true
         end
+        if doc.type == 'doc.overload' then
+            vm.setNode(source, vm.compileNode(doc))
+        end
     end
     return false
 end
 
----@param source parser.object
----@param key any
+---@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, pushResult)
     local fields
-    if key then
-        fields = vm.getLocalSourcesSets(source, key)
+    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
@@ -76,7 +103,7 @@ local function searchFieldByLocalID(source, key, 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
@@ -95,7 +122,7 @@ end
 
 ---@param suri uri
 ---@param source parser.object
----@param key any
+---@param key string|vm.global|vm.ANY
 ---@param pushResult fun(res: parser.object, markDoc?: boolean)
 local function searchFieldByGlobalID(suri, source, key, pushResult)
     local node = vm.getGlobalNode(source)
@@ -103,7 +130,7 @@ local function searchFieldByGlobalID(suri, source, key, pushResult)
         return
     end
     if node.cate == 'variable' then
-        if key then
+        if key ~= vm.ANY then
             if type(key) ~= 'string' then
                 return
             end
@@ -135,14 +162,14 @@ local searchFieldSwitch = util.switch()
             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)
@@ -156,7 +183,7 @@ local searchFieldSwitch = util.switch()
                     hasFiled = true
                     pushResult(field)
                 end
-                if key == nil then
+                if key == vm.ANY then
                     pushResult(field)
                 end
             end
@@ -178,11 +205,11 @@ local searchFieldSwitch = util.switch()
             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)
@@ -197,27 +224,27 @@ local searchFieldSwitch = util.switch()
                 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 == nil
+                        if key == vm.ANY
                         or fn[1] == key then
-                            pushResult(field)
+                            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
@@ -225,7 +252,7 @@ local searchFieldSwitch = util.switch()
     : case 'global'
     : 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
@@ -255,7 +282,7 @@ local searchFieldSwitch = util.switch()
 
 ---@param suri uri
 ---@param object vm.global
----@param key? string|number|integer|boolean|vm.global
+---@param key string|number|integer|boolean|vm.global|vm.ANY
 ---@param pushResult fun(field: vm.object, isMark?: boolean)
 function vm.getClassFields(suri, object, key, pushResult)
     local mark = {}
@@ -284,7 +311,7 @@ function vm.getClassFields(suri, object, key, pushResult)
                     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)
@@ -293,7 +320,7 @@ function vm.getClassFields(suri, object, key, pushResult)
                         end
                         goto CONTINUE
                     end
-                    if key == nil then
+                    if key == vm.ANY then
                         pushResult(field, true)
                         goto CONTINUE
                     end
@@ -351,7 +378,7 @@ function vm.getClassFields(suri, object, key, pushResult)
                             local fieldKey = guide.getKeyName(field)
                             if fieldKey then
                                 if  not searchedFields[fieldKey]
-                                and guide.isSet(field) then
+                                and guide.isAssign(field) then
                                     hasFounded[fieldKey] = true
                                     pushResult(field, true)
                                 end
@@ -371,7 +398,7 @@ function vm.getClassFields(suri, object, key, pushResult)
                                 local fieldKey = guide.getKeyName(field)
                                 if fieldKey then
                                     if  not searchedFields[fieldKey]
-                                    and guide.isSet(field) then
+                                    and guide.isAssign(field) then
                                         hasFounded[fieldKey] = true
                                         pushResult(field, true)
                                     end
@@ -392,10 +419,10 @@ function vm.getClassFields(suri, object, key, pushResult)
                         local fieldKey = guide.getKeyName(field)
                         if fieldKey and not searchedFields[fieldKey] then
                             if  not searchedFields[fieldKey]
-                            and guide.isSet(field)
+                            and guide.isAssign(field)
                             and field.value then
-                                if  vm.getLocalID(field)
-                                and vm.getLocalID(field) == vm.getLocalID(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
@@ -430,7 +457,7 @@ function vm.getClassFields(suri, object, key, pushResult)
 
     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)
@@ -459,11 +486,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
@@ -496,7 +525,7 @@ local function getReturnOfSetMetaTable(args)
         node:merge(vm.compileNode(tbl))
     end
     if mt then
-        vm.compileByParentNode(mt, '__index', function (src)
+        vm.compileByParentNodeAll(mt, '__index', function (src)
             for n in vm.compileNode(src):eachObject() do
                 if n.type == 'global'
                 or n.type == 'local'
@@ -507,6 +536,8 @@ local function getReturnOfSetMetaTable(args)
             end
         end)
     end
+    --过滤nil
+   node:remove 'nil'
     return node
 end
 
@@ -538,7 +569,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
@@ -553,6 +584,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,
@@ -568,7 +600,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
@@ -613,7 +645,6 @@ local function bindAs(source)
     local doc = ases[index]
     if doc and doc.touch == source.finish then
         local asNode = vm.compileNode(doc.as)
-        asNode.resolved = true
         vm.setNode(source, asNode, true)
         return true
     end
@@ -621,10 +652,11 @@ local function bindAs(source)
     return false
 end
 
----@param source parser.object
----@param key? string|vm.global
----@param pushResult fun(source: parser.object)
-function vm.compileByParentNode(source, key, 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 = {}
@@ -677,18 +709,43 @@ function vm.compileByParentNode(source, key, 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
@@ -714,6 +771,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
@@ -836,10 +898,13 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex)
                                 for i = fixIndex + 1, myIndex - 1 do
                                     args[#args+1] = call.args[i]
                                 end
-                                fn = generic:resolve(guide.getUri(call), args)
+                                local resolvedNode = generic:resolve(guide.getUri(call), args)
+                                vm.setNode(arg, resolvedNode)
+                                goto CONTINUE
                             end
                         end
                         vm.setNode(arg, fn)
+                        ::CONTINUE::
                     end
                 end
             end
@@ -907,9 +972,10 @@ end
 
 ---@param source parser.object
 ---@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
@@ -940,9 +1006,11 @@ local function compileForVars(source, target)
                 local node = getReturn(source._iterator, i, source._iterArgs)
                 node:removeOptional()
                 vm.setNode(loc, node)
+                return true
             end
         end
     end
+    return false
 end
 
 ---@param source parser.object
@@ -951,7 +1019,7 @@ local function compileLocal(source)
 
     local hasMarkDoc
     if source.bindDocs then
-        hasMarkDoc = bindDocs(source)
+        hasMarkDoc = vm.bindDocs(source)
     end
     local hasMarkParam
     if not hasMarkDoc then
@@ -972,17 +1040,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
@@ -994,6 +1052,7 @@ local function compileLocal(source)
             vm.setNode(source, vm.compileNode(setfield.node))
         end
     end
+
     if source.parent.type == 'funcargs' and not hasMarkDoc and not hasMarkParam then
         local func = source.parent.parent
         -- local call ---@type fun(f: fun(x: number));call(function (x) end) --> x -> number
@@ -1018,22 +1077,53 @@ local function compileLocal(source)
             vm.setNode(source, vm.declareGlobal('type', 'any'))
         end
     end
+
     -- for x in ... do
     if source.parent.type == 'in' then
         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
 
-    myNode: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
@@ -1079,6 +1169,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
@@ -1086,6 +1179,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'
@@ -1145,13 +1248,14 @@ local compilerSwitch = util.switch()
         end
 
         -- { f = function (<?x?>) end }
-        if guide.isSet(parent) then
+        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
@@ -1163,84 +1267,39 @@ local compilerSwitch = util.switch()
     ---@async
     ---@param source parser.object
     : call(function (source)
-        vm.launchRunner(source, function ()
-            local myNode = vm.getNode(source)
-            ---@cast myNode -?
-            myNode:setData('resolving', true)
-
-            if source.ref then
-                for _, ref in ipairs(source.ref) do
-                    if ref.type == 'getlocal'
-                    or ref.type == 'setlocal' then
-                        vm.setNode(ref, myNode, true)
-                    end
-                end
-            end
-            compileLocal(source)
-
-            myNode.resolved = true
-        end, function ()
-            local myNode = vm.getNode(source)
-            ---@cast myNode -?
-            myNode:setData('resolving', nil)
-            local hasMark = vm.getNode(source):getData 'hasDefined'
-            if source.ref and 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, 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), true)
-                        vm.setNode(src, node:copy():asTable())
-                    else
-                        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)
-                node.resolved = true
-                matchCall(src)
-            end
-        end)
-
-        vm.waitResolveRunner(source)
+        compileLocal(source)
     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.waitResolveRunner(source)
+        vm.setNode(source, node, true)
     end)
     : case 'setfield'
     : case 'setmethod'
@@ -1249,7 +1308,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)?
@@ -1260,6 +1322,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)
@@ -1272,53 +1335,44 @@ local compilerSwitch = util.switch()
                     ---@cast k vm.global
                     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, 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.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, 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)
+        if not key then
+            return
+        end
         vm.compileByParentNode(source.node, key, function (src)
             vm.setNode(source, vm.compileNode(src))
         end)
@@ -1328,22 +1382,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), 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'
-                or src.type == 'doc.type.name' then
-                    hasMarkDoc = true
-                    vm.setNode(source, vm.compileNode(src))
+                or src.type == 'doc.type.field' then
+                    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)
@@ -1359,6 +1431,7 @@ local compilerSwitch = util.switch()
             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))
@@ -1383,6 +1456,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)
@@ -1447,7 +1524,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
@@ -1489,6 +1566,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
@@ -1549,7 +1641,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)
@@ -1570,7 +1662,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)
@@ -1598,7 +1690,7 @@ local compilerSwitch = util.switch()
             if state.type == 'doc.return'
             or state.type == 'doc.param' then
                 local func = state.bindSource
-                if func.type == 'function' then
+                if func and func.type == 'function' then
                     local node = guide.getFunctionSelfNode(func)
                     if node then
                         vm.setNode(source, vm.compileNode(node))
@@ -1652,6 +1744,7 @@ local compilerSwitch = util.switch()
         end
     end)
     : case 'doc.class.name'
+    : case 'doc.alias.name'
     : call(function (source)
         vm.setNode(source, vm.compileNode(source.parent))
     end)
@@ -1730,7 +1823,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
@@ -1740,7 +1833,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
@@ -1748,99 +1841,65 @@ local compilerSwitch = util.switch()
         end
         vm.binarySwitch(source.op.type, 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 = vm.getGlobalNode(source)
-    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
-        end
-        -- Set all globals node first to avoid recursive
-        for _, set in ipairs(global:getSets(uri)) do
-            vm.setNode(set, globalNode, true)
-        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))
+    : 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
-        for _, set in ipairs(global:getSets(uri)) do
-            vm.setNode(set, globalNode, true)
-        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
-                                globalNode:merge(vm.compileNode(ext))
+        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
-            end
-            if set.type == 'doc.alias' then
-                if not vm.getGeneric(set.extends) then
-                    globalNode:merge(vm.compileNode(set.extends))
+                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
+    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
 
 local nodeSwitch;nodeSwitch = util.switch()
@@ -1902,7 +1961,7 @@ end
 
 ---@param source vm.object
 local function compileByParentNode(source)
-    if not vm.getNode(source):isEmpty() then
+    if vm.getNode(source):isTyped() then
         return
     end
     vm.compileByNodeChain(source, function (result)
@@ -1910,7 +1969,7 @@ local function compileByParentNode(source)
     end)
 end
 
----@param source vm.object
+---@param source vm.node.object | vm.variable
 ---@return vm.node
 function vm.compileNode(source)
     if not source then
@@ -1921,34 +1980,20 @@ function vm.compileNode(source)
         end
     end
 
-    if source.type == 'getlocal' then
-        ---@cast source parser.object
-        vm.storeWaitingRunner(source)
-        ---@diagnostic disable-next-line: await-in-sync
-        vm.waitResolveRunner(source)
-    end
-
     local cache = vm.getNode(source)
     if cache ~= nil then
         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)
-    compileByGlobal(source)
+    vm.compileByGlobal(source)
+    vm.compileByVariable(source)
     compileByNode(source)
     compileByParentNode(source)
     matchCall(source)
 
     local node = vm.getNode(source)
     ---@cast node -?
-    node.resolved = true
     return node
 end
diff --git a/script/vm/def.lua b/script/vm/def.lua
index 9bc6159b3..5388a3849 100644
--- a/script/vm/def.lua
+++ b/script/vm/def.lua
@@ -33,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
@@ -78,7 +78,7 @@ function vm.getDefs(source)
         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
diff --git a/script/vm/doc.lua b/script/vm/doc.lua
index b292bc3c8..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<string, boolean>
+---@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<string, boolean>?
 function vm.getValidVersions(doc)
@@ -414,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 9e883505c..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)
@@ -31,7 +31,7 @@ local function searchByNode(source, pushResult, mark)
     end
     mark[source] = true
     local uri = guide.getUri(source)
-    vm.compileByParentNode(source, nil, function (field)
+    vm.compileByParentNode(source, vm.ANY, function (field)
         searchByNodeSwitch(field.type, uri, field, pushResult)
     end)
     vm.compileByNodeChain(source, function (src)
diff --git a/script/vm/function.lua b/script/vm/function.lua
index bdd7e229a..c6df63498 100644
--- a/script/vm/function.lua
+++ b/script/vm/function.lua
@@ -372,8 +372,14 @@ function vm.isVarargFunctionWithOverloads(func)
     if not func.args then
         return false
     end
-    if not func.args[1] or func.args[1].type ~= '...' then
-        return false
+    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
diff --git a/script/vm/generic.lua b/script/vm/generic.lua
index 4ac7374d3..ed832d9bd 100644
--- a/script/vm/generic.lua
+++ b/script/vm/generic.lua
@@ -124,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)
diff --git a/script/vm/global.lua b/script/vm/global.lua
index 5ce7c8521..e830f6d8e 100644
--- a/script/vm/global.lua
+++ b/script/vm/global.lua
@@ -1,13 +1,23 @@
 local util  = require 'utility'
 local scope = require 'workspace.scope'
 local guide = require 'parser.guide'
-local files = require 'files'
+local config = require 'config'
 ---@class vm
 local vm    = require 'vm.vm'
 
+---@type table<string, vm.global>
+local allGlobals = {}
+---@type table<uri, table<string, boolean>>
+local globalSubs = util.multiTable(2)
+
+---@class parser.object
+---@field package _globalBase parser.object
+---@field package _globalBaseMap table<string, parser.object>
+---@field global vm.global
+
 ---@class vm.global.link
----@field sets   parser.object[]
----@field hasGet boolean?
+---@field sets parser.object[]
+---@field gets parser.object[]
 
 ---@class vm.global
 ---@field links table<uri, vm.global.link>
@@ -22,17 +32,15 @@ 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
 
 ---@param uri    uri
-function mt:addGet(uri)
+---@param source parser.object
+function mt:addGet(uri, source)
     local link = self.links[uri]
-    link.hasGet = true
+    link.gets[#link.gets+1] = source
 end
 
 ---@param suri uri
@@ -112,18 +120,51 @@ 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
 
@@ -131,11 +172,6 @@ end
 ---@field package _globalNode vm.global|false
 ---@field package _enums?     parser.object[]
 
----@type table<string, vm.global>
-local allGlobals = {}
----@type table<uri, table<string, boolean>>
-local globalSubs = util.multiTable(2)
-
 local compileObject
 local compilerGlobalSwitch = util.switch()
     : case 'local'
@@ -178,7 +214,7 @@ local compilerGlobalSwitch = util.switch()
             return
         end
         local global = vm.declareGlobal('variable', name, uri)
-        global:addGet(uri)
+        global:addGet(uri, source)
         source._globalNode = global
 
         local nxt = source.next
@@ -236,7 +272,7 @@ local compilerGlobalSwitch = util.switch()
         end
         local uri    = guide.getUri(source)
         local global = vm.declareGlobal('variable', name, uri)
-        global:addGet(uri)
+        global:addGet(uri, source)
         source._globalNode = global
 
         local nxt = source.next
@@ -262,7 +298,7 @@ local compilerGlobalSwitch = util.switch()
                         global:addSet(uri, source)
                         source.value = source.args[3]
                     else
-                        global:addGet(uri)
+                        global:addGet(uri, source)
                     end
                     source._globalNode = global
 
@@ -336,16 +372,36 @@ local compilerGlobalSwitch = util.switch()
             return
         end
         source._enums = {}
-        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)
+        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
+            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
             end
         end
@@ -361,7 +417,7 @@ local compilerGlobalSwitch = util.switch()
             return
         end
         local type = vm.declareGlobal('type', name, uri)
-        type:addGet(uri)
+        type:addGet(uri, source)
         source._globalNode = type
     end)
     : case 'doc.extends.name'
@@ -369,7 +425,7 @@ local compilerGlobalSwitch = util.switch()
         local uri  = guide.getUri(source)
         local name = source[1]
         local class = vm.declareGlobal('type', name, uri)
-        class:addGet(uri)
+        class:addGet(uri, source)
         source._globalNode = class
     end)
 
@@ -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
@@ -505,40 +588,70 @@ function vm.getEnums(source)
 end
 
 ---@param source parser.object
-local function compileSelf(source)
-    if source.parent.type ~= 'funcargs' then
-        return
+---@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
@@ -563,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
@@ -592,18 +693,7 @@ local function dropUri(uri)
     end
 end
 
----@async
-files.watch(function (ev, uri)
-    if ev == 'update' then
-        dropUri(uri)
-    end
-    if ev == 'remove' then
-        dropUri(uri)
-    end
-    if ev == 'compile' then
-        local state = files.getLastState(uri)
-        if state then
-            compileAst(state.ast)
-        end
-    end
-end)
+return {
+    compileAst = compileAst,
+    dropUri    = dropUri,
+}
diff --git a/script/vm/infer.lua b/script/vm/infer.lua
index b9dfb29a4..f2673ed3e 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<string, boolean>
----@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
@@ -60,7 +63,7 @@ local viewNodeSwitch;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
@@ -134,11 +137,13 @@ local viewNodeSwitch;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
@@ -150,10 +155,6 @@ local viewNodeSwitch;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] = '{ '
@@ -242,7 +243,17 @@ local viewNodeSwitch;viewNodeSwitch = util.switch()
 ---@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
@@ -257,10 +268,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
@@ -291,9 +299,14 @@ function mt:_trim()
 end
 
 ---@param uri uri
----@return table<string, true>
 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
@@ -304,8 +317,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
@@ -314,9 +327,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
+                                self._drop[view] = true
                             end
                         end
                     end
@@ -326,7 +339,6 @@ function mt:_eraseAlias(uri)
             ::CONTINUE::
         end
     end
-    return drop
 end
 
 ---@param uri uri
@@ -374,9 +386,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
 
@@ -387,20 +401,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
@@ -432,13 +455,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
 
@@ -448,21 +481,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?
@@ -477,7 +500,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
@@ -525,7 +553,8 @@ 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
@@ -537,23 +566,19 @@ function vm.viewKey(source, uri)
         if #source.types == 1 then
             return vm.viewKey(source.types[1], uri)
         else
-            local key = vm.viewObject(source, uri)
-            return '[' .. key .. ']'
+            local key = vm.getInfer(source):view(uri)
+            return '[' .. key .. ']', key
         end
     end
-    if source.type == 'tableindex' then
+    if source.type == 'tableindex'
+    or source.type == 'setindex'
+    or source.type == 'getindex' then
         local index = source.index
-        local name = vm.getKeyName(index)
+        local name = vm.getInfer(index):viewLiterals()
         if not name then
             return nil
         end
-        local key
-        if index.type == 'string' then
-            key = util.viewString(name, index[2])
-        else
-            key = util.viewLiteral(name)
-        end
-        return ('[%s]'):format(key), name
+        return ('[%s]'):format(name), name
     end
     if source.type == 'tableexp' then
         return ('[%d]'):format(source.tindex), source.tindex
@@ -565,7 +590,11 @@ function vm.viewKey(source, uri)
         return vm.viewKey(source.name, uri)
     end
     if source.type == 'doc.type.name' then
-        return '[' .. source[1] .. ']'
+        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
diff --git a/script/vm/init.lua b/script/vm/init.lua
index 7b69a7eba..f1d3292a3 100644
--- a/script/vm/init.lua
+++ b/script/vm/init.lua
@@ -11,14 +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 1c3fd3569..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 package _localID string|false
----@field package _localIDs table<string, { sets: parser.object[], gets: parser.object[] }>
-
-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 2e408128a..bc1dfcb10 100644
--- a/script/vm/node.lua
+++ b/script/vm/node.lua
@@ -9,18 +9,21 @@ local util     = require 'utility'
 ---@type table<vm.object, vm.node>
 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<vm.node|string, vm.node>
+---@field undefinedGlobal boolean?
 local mt = {}
 mt.__index    = mt
 mt.id         = 0
 mt.type       = 'vm.node'
 mt.optional   = nil
 mt.data       = nil
-mt.resolved   = nil
+mt.hasDefined = nil
+mt.originNode = nil
 
 ---@param node vm.node | vm.node.object
 ---@return vm.node
@@ -56,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
@@ -70,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
@@ -291,7 +292,7 @@ function mt:narrow(uri, 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
@@ -397,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
@@ -428,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]
diff --git a/script/vm/operator.lua b/script/vm/operator.lua
index cb27d33d2..7ce2b30d0 100644
--- a/script/vm/operator.lua
+++ b/script/vm/operator.lua
@@ -73,15 +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))
-        break
         ::CONTINUE::
     end
     return result
@@ -119,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,
@@ -148,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,
@@ -164,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,
@@ -199,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)
@@ -213,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,
@@ -237,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,
@@ -246,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 '+'
@@ -273,6 +290,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   = (op == '//' or math.type(result) == 'integer') and 'integer' or 'number',
                 start  = source.start,
@@ -293,20 +311,38 @@ vm.binarySwitch = util.switch()
                 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
-                vm.setNode(source, node or vm.declareGlobal('type', 'integer'))
-                return
+                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 '..'
@@ -334,6 +370,7 @@ vm.binarySwitch = util.switch()
                     end
                 end
             end
+            ---@diagnostic disable-next-line: missing-fields
             vm.setNode(source, {
                 type   = 'string',
                 start  = source.start,
@@ -342,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 '>'
@@ -359,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 2740666b1..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
@@ -86,7 +89,7 @@ local function searchWord(source, pushResult, defMap, fileNotify)
         if not text then
             return
         end
-        if not text:match(key) then
+        if not text:find(key, 1, true) then
             return
         end
         local state = files.getState(uri)
@@ -104,29 +107,28 @@ local function searchWord(source, pushResult, defMap, fileNotify)
                     await.delay()
                 end
             end)
-        else
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getfield', 'setfield'}, function (src)
-                if src.field and src.field[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getmethod', 'setmethod'}, function (src)
-                if src.method and src.method[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getindex', 'setindex'}, function (src)
-                if src.index and src.index.type == 'string' and src.index[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
         end
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getfield', 'setfield'}, function (src)
+            if src.field and src.field[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getmethod', 'setmethod'}, function (src)
+            if src.method and src.method[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getindex', 'setindex'}, function (src)
+            if src.index and src.index.type == 'string' and src.index[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
     end
 
     searchInAllFiles(guide.getUri(source), findWord, fileNotify)
@@ -228,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)
@@ -285,7 +287,10 @@ local function searchByDef(source, pushResult)
     local defs = vm.getDefs(source)
     for _, def in ipairs(defs) do
         pushResult(def)
-        if not guide.isLiteral(def) then
+        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
diff --git a/script/vm/runner.lua b/script/vm/runner.lua
deleted file mode 100644
index 8e2645210..000000000
--- a/script/vm/runner.lua
+++ /dev/null
@@ -1,565 +0,0 @@
----@class vm
-local vm        = require 'vm.vm'
-local guide     = require 'parser.guide'
-local linked    = require 'linked-table'
-
----@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<parser.object, true>
----@field _main     parser.object
----@field _uri      uri
-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)
-                local checkerName = vm.getNodeName(checker)
-                if checkerName then
-                    topNode = topNode:copy()
-                    if action.op.type == '==' then
-                        topNode:narrow(self._uri, checkerName)
-                        if outNode then
-                            outNode:removeNode(checkerNode)
-                        end
-                    else
-                        topNode:removeNode(checkerNode)
-                        if outNode then
-                            outNode:narrow(self._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    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(self._uri, checker[1])
-                    if outNode then
-                        outNode:remove(checker[1])
-                    end
-                else
-                    topNode:remove(checker[1])
-                    if outNode then
-                        outNode:narrow(self._uri, 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(self._uri, checker[1])
-                            if outNode then
-                                outNode:remove(checker[1])
-                            end
-                        else
-                            topNode:remove(checker[1])
-                            if outNode then
-                                outNode:narrow(self._uri, 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
-
----@alias runner.info { target?: parser.object, loc: parser.object }
-
----@type thread?
-local masterRunner = nil
----@type table<thread, runner.info>
-local runnerInfo = setmetatable({}, {
-    __mode = 'k',
-    __index = function (self, k)
-        self[k] = {}
-        return self[k]
-    end
-})
----@type linked-table?
-local runnerList = nil
-
----@async
----@param info runner.info
-local function waitResolve(info)
-    while true do
-        if not info.target then
-            break
-        end
-        if info.target.node == info.loc then
-            break
-        end
-        local node = vm.getNode(info.target)
-        if node and node.resolved then
-            break
-        end
-        coroutine.yield()
-    end
-    info.target = nil
-end
-
-local function resolveDeadLock()
-    if not runnerList then
-        return
-    end
-
-    ---@type runner.info[]
-    local infos = {}
-    for runner in runnerList:pairs() do
-        local info = runnerInfo[runner]
-        infos[#infos+1] = info
-    end
-
-    table.sort(infos, function (a, b)
-        local uriA = guide.getUri(a.loc)
-        local uriB = guide.getUri(b.loc)
-        if uriA ~= uriB then
-            return uriA < uriB
-        end
-        return a.loc.start < b.loc.start
-    end)
-
-    local firstTarget = infos[1].target
-    ---@cast firstTarget -?
-    local firstNode = vm.setNode(firstTarget, vm.getNode(firstTarget):copy(), true)
-    firstNode.resolved = true
-    firstNode:setData('resolvedByDeadLock', true)
-end
-
----@async
----@param loc parser.object
----@param start fun()
----@param finish fun()
----@param callback vm.runner.callback
-function vm.launchRunner(loc, start, finish, callback)
-    local locNode = vm.getNode(loc)
-    if not locNode then
-        return
-    end
-
-    local function resumeMaster()
-        for i = 1, 10010 do
-            if not runnerList or runnerList:getSize() == 0 then
-                return
-            end
-            local deadLock = true
-            for runner in runnerList:pairs() do
-                local info = runnerInfo[runner]
-                local waitingSource = info.target
-                if coroutine.status(runner) == 'suspended' then
-                    local suc, err = coroutine.resume(runner)
-                    if not suc then
-                        log.error(debug.traceback(runner, err))
-                    end
-                else
-                    runnerList:pop(runner)
-                    deadLock = false
-                end
-                if not waitingSource or waitingSource ~= info.target then
-                    deadLock = false
-                end
-            end
-            if runnerList:getSize() == 0 then
-                return
-            end
-            if deadLock then
-                resolveDeadLock()
-            end
-            if i == 10000 then
-                local lines = {}
-                lines[#lines+1] = 'Dead lock:'
-                for runner in runnerList:pairs() do
-                    local info = runnerInfo[runner]
-                    lines[#lines+1] = '==============='
-                    lines[#lines+1] = string.format('Runner `%s` at %d(%s)'
-                        , info.loc[1]
-                        , info.loc.start
-                        , guide.getUri(info.loc)
-                    )
-                    lines[#lines+1] = string.format('Waiting `%s` at %d(%s)'
-                        , info.target[1]
-                        , info.target.start
-                        , guide.getUri(info.target)
-                    )
-                end
-                local msg = table.concat(lines, '\n')
-                log.error(msg)
-            end
-        end
-    end
-
-    local function launch()
-        start()
-        if not loc.ref then
-            finish()
-            return
-        end
-        local main = guide.getParentBlock(loc)
-        if not main then
-            finish()
-            return
-        end
-        local self = setmetatable({
-            _loc      = loc,
-            _casts    = {},
-            _mark     = {},
-            _has      = {},
-            _main     = main,
-            _uri      = guide.getUri(loc),
-            _callback = callback,
-        }, mt)
-
-        self:collect()
-
-        self:lookIntoBlock(main, locNode:copy())
-
-        locNode:setData('runner', nil)
-
-        finish()
-    end
-
-    local co = coroutine.create(launch)
-    locNode:setData('runner', co)
-    local info = runnerInfo[co]
-    info.loc = loc
-
-    if not runnerList then
-        runnerList = linked()
-    end
-    runnerList:pushTail(co)
-
-    if not masterRunner then
-        masterRunner = coroutine.running()
-        resumeMaster()
-        masterRunner = nil
-        return
-    end
-end
-
----@async
----@param source parser.object
-function vm.waitResolveRunner(source)
-    local myNode = vm.getNode(source)
-    if myNode and myNode.resolved then
-        return
-    end
-
-    local running = coroutine.running()
-    if not masterRunner or running == masterRunner then
-        return
-    end
-
-    local info = runnerInfo[running]
-
-    local targetLoc
-    if source.type == 'getlocal' then
-        targetLoc = source.node
-    elseif source.type == 'local'
-    or     source.type == 'self' then
-        targetLoc = source
-        info.target = info.target or source
-    else
-        error('Unknown source type: ' .. source.type)
-    end
-
-    local targetNode = vm.getNode(targetLoc)
-    if not targetNode then
-        -- Wait for compiling local by `compiler`
-        return
-    end
-
-    waitResolve(info)
-end
-
----@param source parser.object
-function vm.storeWaitingRunner(source)
-    local sourceNode = vm.getNode(source)
-    if sourceNode and sourceNode.resolved then
-        return
-    end
-
-    local running = coroutine.running()
-    local info = runnerInfo[running]
-    info.target = source
-end
diff --git a/script/vm/sign.lua b/script/vm/sign.lua
index 10b08884d..1f4344758 100644
--- a/script/vm/sign.lua
+++ b/script/vm/sign.lua
@@ -193,7 +193,11 @@ function mt:resolve(uri, args)
                     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
@@ -219,7 +223,7 @@ function mt:resolve(uri, args)
                     goto CONTINUE
                 end
             end
-            local view = vm.viewObject(n, uri)
+            local view = vm.getInfer(n):view(uri)
             if knownTypes[view] then
                 goto CONTINUE
             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<parser.object, true>
+---@field getMap    table<parser.object, true>
+---@field careMap   table<parser.object, true>
+---@field mark      table<parser.object, true>
+---@field casts     parser.object[]
+---@field nodes     table<parser.object, vm.node|false>
+---@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 ae5c05dba..910d79600 100644
--- a/script/vm/type.lua
+++ b/script/vm/type.lua
@@ -5,6 +5,10 @@ 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
@@ -139,6 +143,9 @@ end
 ---@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
@@ -153,11 +160,14 @@ local function checkChildEnum(childName, parent , uri, mark, errs)
     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
 
@@ -217,6 +227,9 @@ local function checkValue(parent, child, mark, errs)
 
     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)
@@ -294,7 +307,9 @@ function vm.isSubType(uri, child, parent, mark, errs)
                 end
             end
             if hasKnownType > 0 then
-                if errs and hasKnownType > 1 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
@@ -369,7 +384,9 @@ function vm.isSubType(uri, child, parent, mark, errs)
             end
         end
         if hasKnownType > 0 then
-            if errs and hasKnownType > 1 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
@@ -455,6 +472,7 @@ function vm.isSubType(uri, child, parent, mark, errs)
                         if  ext.type == 'doc.extends.name'
                         and (not isBasicType or guide.isBasicType(ext[1]))
                         and vm.isSubType(uri, ext[1], parent, mark, errs) == true then
+                            mark[childName] = nil
                             return true
                         end
                     end
@@ -696,6 +714,7 @@ local ErrorMessageMap = {
 ---@return string
 function vm.viewTypeErrorMessage(uri, errs)
     local lines = {}
+    local mark  = {}
     local index = 1
     while true do
         local name = errs[index]
@@ -720,21 +739,31 @@ function vm.viewTypeErrorMessage(uri, errs)
                 lparams[paramName] = 'table'
             elseif value.type == 'generic' then
                 ---@cast value vm.generic
-                lparams[paramName] = vm.viewObject(value, uri)
+                lparams[paramName] = vm.getInfer(value):view(uri)
+            elseif value.type == 'variable' then
             else
-                ---@cast value -string, -vm.global, -vm.node, -vm.generic
+                ---@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.viewObject(value, uri)
+                    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)
-        lines[#lines+1] = '- ' .. line
+        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
-    util.revertTable(lines)
-    return table.concat(lines, '\n')
 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<string, vm.variable>
+
+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
index 0a0144b79..d13ecf1f6 100644
--- a/script/vm/visible.lua
+++ b/script/vm/visible.lua
@@ -7,9 +7,10 @@ local glob     = require 'glob'
 ---@class parser.object
 ---@field package _visibleType? parser.visibleType
 
----@param source parser.object
----@return parser.visibleType
-function vm.getVisibleType(source)
+local function getVisibleType(source)
+    if guide.isLiteral(source) then
+        return 'public'
+    end
     if source._visibleType then
         return source._visibleType
     end
@@ -55,6 +56,27 @@ function vm.getVisibleType(source)
     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)
@@ -76,7 +98,7 @@ end
 ---@return vm.global?
 function vm.getDefinedClass(suri, source)
     source = guide.getSelfNode(source) or source
-    local sets = vm.getLocalSourcesSets(source)
+    local sets = vm.getVariableSets(source)
     if sets then
         for _, set in ipairs(sets) do
             if set.bindDocs then
diff --git a/script/workspace/loading.lua b/script/workspace/loading.lua
index 28c76561c..5ddc4bdab 100644
--- a/script/workspace/loading.lua
+++ b/script/workspace/loading.lua
@@ -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
diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua
index f65af8684..c319cbad6 100644
--- a/script/workspace/require-path.lua
+++ b/script/workspace/require-path.lua
@@ -59,16 +59,29 @@ 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 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 '^[/\\]'
                         or searcher:match '^%a+%:'
-        searcher = workspace.normalize(searcher)
+        searcher = files.normalize(searcher)
         if searcher:sub(1, 1) == '.' then
             strict = true
         end
@@ -145,7 +158,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)
@@ -157,22 +170,39 @@ end
 --- 查找符合指定require name的所有uri
 ---@param name string
 ---@return uri[]
----@return table<uri, string>
+---@return table<uri, string>?
 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, '/')
     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 fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
-        fspath = workspace.normalize(fspath)
+        fspath = files.normalize(fspath)
         local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '')
         for uri in files.eachFile(self.scp.uri) do
             if  not searcherMap[uri]
-            and util.stringEndWith(uri, tail) then
+            and not excludes[uri]
+            and util.stringEndWith(uri, tail)
+            and (not vm.isMetaFile(uri) or vm.isMetaFileRequireable(uri)) then
                 local parentUri = files.getLibraryUri(self.scp.uri, uri) or self.scp.uri
                 if parentUri == nil or parentUri == '' then
                     parentUri = furi.encode '/'
@@ -182,7 +212,7 @@ function mt:searchUrisByRequireName(name)
                 or relative == '/'
                 or relative == '' then
                     results[#results+1] = uri
-                    searcherMap[uri] = workspace.normalize(relative .. searcher)
+                    searcherMap[uri] = files.normalize(relative .. searcher)
                 end
             end
         end
@@ -223,7 +253,7 @@ function mt:findUrisByRequireName(suri, name)
     for _, uri in ipairs(cache.results) do
         if uri ~= suri then
             results[#results+1] = uri
-            searcherMap[uri] = cache.searcherMap[uri]
+            searcherMap[uri] = cache.searcherMap and cache.searcherMap[uri]
         end
     end
     return results, searcherMap
@@ -242,6 +272,8 @@ end
 
 ---@param uri uri
 ---@param name string
+---@return uri[]
+---@return table<uri, string>?
 function m.findUrisByRequireName(uri, name)
     local scp = scope.getScope(uri)
     ---@type require-manager
@@ -262,7 +294,7 @@ function m.isMatchedUri(suri, uri, name)
 
     for _, searcher in ipairs(searchers) do
         local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
-        fspath = workspace.normalize(fspath)
+        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
diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua
index b6be6fc59..97518e84b 100644
--- a/script/workspace/workspace.lua
+++ b/script/workspace/workspace.lua
@@ -45,16 +45,15 @@ 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)))
-        scp:set('bad root', true)
+        if not FORCE_ACCEPT_WORKSPACE then
+            client.showMessage('Error', lang.script('WORKSPACE_NOT_ALLOWED', furi.decode(uri)))
+            scp:set('bad root', true)
+        end
     end
 end
 
@@ -87,26 +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 ()
-            if fs.is_directory(path) then
+            if fs.is_directory(fs.path(path)) then
                 result = 'directory'
+                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 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
@@ -201,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 = {}
@@ -225,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)
@@ -308,7 +328,13 @@ function m.awaitPreload(scp)
 
     if scp.uri and not scp:get('bad root') then
         log.info('Scan files at:', scp:getName())
-        scp:gc(fw.watch(m.normalize(furi.decode(scp.uri))))
+        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)
@@ -326,7 +352,13 @@ function m.awaitPreload(scp)
     for _, libMatcher in ipairs(librarys) do
         log.info('Scan library at:', libMatcher.uri)
         local count = 0
-        scp:gc(fw.watch(furi.decode(libMatcher.uri)))
+        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)
@@ -371,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
@@ -435,14 +433,14 @@ function m.getRelativePath(uriOrPath)
     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
-        return m.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true
+        return files.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true
     else
-        return m.normalize(path):gsub('^[/\\]+', ''), false
+        return files.normalize(path):gsub('^[/\\]+', ''), false
     end
 end
 
@@ -473,10 +471,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
@@ -497,6 +491,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)
@@ -536,11 +532,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
diff --git a/test.lua b/test.lua
index 9e1369345..6b3bc16de 100644
--- a/test.lua
+++ b/test.lua
@@ -82,12 +82,14 @@ local function main()
 
     --log.print = true
 
-    TESTURI = furi.encode('/unittest.lua')
+    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 '/'
+        local rootUri = furi.encode(TESTROOT)
         client:initialize {
             rootUri = rootUri,
         }
@@ -105,6 +107,7 @@ local function main()
 
     test 'tclient'
     test 'full'
+    test 'plugins.ffi.test'
 end
 
 loadAllLibs()
diff --git a/test/code_action/init.lua b/test/code_action/init.lua
index 2329ce4c7..264cfacf4 100644
--- a/test/code_action/init.lua
+++ b/test/code_action/init.lua
@@ -2,38 +2,40 @@ 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)
@@ -47,6 +49,32 @@ function TEST(script)
     end
 end
 
+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 _ <close> = 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
+
 TEST [[
 print(<?a?>, b, c)
 ]]
@@ -154,3 +182,97 @@ local t = {
 --        edit  = EXISTS,
 --    },
 --}
+
+TEST_CROSSFILE {
+[[
+    <?unrequiredModule?>.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 {
+[[
+    <?myModule?>.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/completion/common.lua b/test/completion/common.lua
index 0bf9c606a..bd3172593 100644
--- a/test/completion/common.lua
+++ b/test/completion/common.lua
@@ -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,
@@ -1075,12 +1075,12 @@ z<??>
 ]]
 {
     {
-        label = 'z.b.c',
+        label = "'z.b.c'",
         kind = define.CompletionItemKind.Field,
         textEdit = {
             start = 20000,
             finish = 20001,
-            newText = '_ENV["z.b.c"]',
+            newText = "_ENV['z.b.c']",
         },
     },
 }
@@ -1094,12 +1094,12 @@ z<??>
 ]]
 {
     {
-        label = 'z.b.c',
+        label = "'z.b.c'",
         kind = define.CompletionItemKind.Field,
         textEdit = {
             start = 20000,
             finish = 20001,
-            newText = '_G["z.b.c"]',
+            newText = "_G['z.b.c']",
         },
     },
 }
@@ -3538,7 +3538,7 @@ TEST [[
 require '<??>'
 ]]
 (function (results)
-    assert(#results == 11, require 'utility'.dump(results))
+    assert(#results == 9, require 'utility'.dump(results))
 end)
 
 TEST [[
@@ -3815,6 +3815,32 @@ f(<??>)
     },
 }
 
+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 [[
 --
 <??>
@@ -4084,3 +4110,280 @@ 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,
+    },
+}
diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua
index 8504758e9..227350cbc 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 _ <close> = 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,6 +108,12 @@ 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
@@ -120,6 +130,37 @@ local function WITH_CONFIG(cfg, f)
     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',
@@ -438,6 +479,51 @@ TEST {
 
 config.set(nil, 'Lua.runtime.path', originRuntimePath)
 
+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',
@@ -560,6 +646,8 @@ TEST {
     completion = nil,
 }
 
+NeedRemoveMeta = true
+
 TEST {
     { path = 'f/a.lua' },
     { path = 'f/b.lua' },
@@ -854,7 +942,7 @@ TEST {
             kind  = CompletionItemKind.Variable,
             detail = 'function',
             description = [[
-从 [myfunc.lua](file:///myfunc.lua) 中导入
+从 [myfunc.lua]($TESTROOTURI$myfunc.lua) 中导入
 
 ```lua
 function (a: any, b: any)
@@ -885,7 +973,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)
diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua
index e924c43fd..b13b57f39 100644
--- a/test/crossfile/definition.lua
+++ b/test/crossfile/definition.lua
@@ -53,6 +53,7 @@ function TEST(datas)
             sourceUri = uri
         end
         files.setText(uri, newScript)
+        files.compileState(uri)
     end
 
     local _ <close> = function ()
diff --git a/test/crossfile/diagnostic.lua b/test/crossfile/diagnostic.lua
index 126a2ab85..1acf3fe2c 100644
--- a/test/crossfile/diagnostic.lua
+++ b/test/crossfile/diagnostic.lua
@@ -47,6 +47,7 @@ function TEST(datas)
         end
         data.content = newScript
         files.setText(uri, newScript)
+        files.compileState(uri)
     end
 
     local _ <close> = function ()
@@ -57,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 = '',
@@ -86,6 +93,7 @@ TEST {
 }
 
 TEST {
+    code = 'different-requires',
     {
         path = 'f/a.lua',
         content = '',
@@ -105,6 +113,7 @@ TEST {
 }
 
 TEST {
+    code = 'different-requires',
     {
         path = 'a.lua',
         content = '',
@@ -124,6 +133,7 @@ TEST {
 }
 
 TEST {
+    code = 'different-requires',
     {
         path = 'a/init.lua',
         content = '',
@@ -143,6 +153,7 @@ TEST {
 }
 
 TEST {
+    code = 'invisible',
     { path = 'a.lua', content = [[
         ---@class A
         ---@field package x string
@@ -155,6 +166,7 @@ TEST {
 }
 
 TEST {
+    code = 'invisible',
     { path = 'a.lua', content = [[
         ---@class A
         ---@field package x string
@@ -168,6 +180,7 @@ TEST {
 }
 
 TEST {
+    code = 'duplicate-doc-field',
     { path = 'a.lua', content = [[
         ---@class A
         ---@field <!x!> number
@@ -179,6 +192,7 @@ TEST {
 }
 
 TEST {
+    code = 'duplicate-set-field',
     { path = 'a.lua', content = [[
         ---@class A
         local mt
@@ -196,6 +210,7 @@ TEST {
 }
 
 TEST {
+    code = 'duplicate-set-field',
     { path = 'a.lua', content = [[
         ---@class A
         local mt
diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua
index c0b1691f1..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 <?"a"?>',
+    },
+    hover = [[
+```lua
+1 个字节
+```]],
+}
+
+TEST {
+    {
+        path = 'a.lua',
+        content = '---@meta xxx',
+    },
+    {
+        path = 'b.lua',
+        content = 'require <?"a"?>',
+    },
+    hover = [[
+```lua
+1 个字节
+```]],
+}
+
+TEST {
+    {
+        path = 'a.lua',
+        content = '---@meta xxx',
+    },
+    {
+        path = 'b.lua',
+        content = 'require <?"xxx"?>',
+    },
+    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 {
@@ -1583,6 +1633,30 @@ TEST {
 ```]]
 }
 
+TEST {
+    {
+        path = 'a.lua',
+        content = [[
+            ---@enum(key) <?A?>
+            local t = {
+                x = 1 << 0,
+                y = 1 << 1,
+                z = 1 << 2,
+            }
+        ]]
+    },
+    hover = [[
+```lua
+(enum) A
+```
+
+---
+
+```lua
+"x" | "y" | "z"
+```]]
+}
+
 TEST {
     {
         path = 'a.lua',
@@ -1645,6 +1719,26 @@ 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:<?f?>
+]] },
+hover = [[
+```lua
+(method) M:f(x: number)
+```
+
+---
+
+```lua
+(method) M:f(x: number, y: number)
+```]]
+}
+
 TEST { {path = 'a.lua', content = [[
 ---@class A
 
@@ -1658,7 +1752,7 @@ local x: unknown
 
 ---
 
-See: [A](file:///a.lua#1#10)  comment1]]
+See: [A](file:///a.lua#1#10) comment1]]
 }
 
 TEST { {path = 'a.lua', content = [[
@@ -1678,8 +1772,8 @@ local x: unknown
 ---
 
 See:
-  * [A](file:///a.lua#1#10)  comment1
-  * [TTT](file:///a.lua#3#0)  comment2]]
+  * [A](file:///a.lua#1#10) comment1
+  * [TTT](file:///a.lua#3#0) comment2]]
 }
 
 TEST { {path = 'a.lua', content = [[
@@ -1705,3 +1799,56 @@ comment2
 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 <?foo?>(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(<?x?>)
+]]},
+hover = [[
+```lua
+local x: integer = 1
+```
+
+---
+
+comment1]]
+}
+
+TEST { {path = 'a.lua', content = [[
+local t = {}
+
+print(<?t?>['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 _ <close> = 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.<?y?>)
+end
+        ]],
+    },
+    infer = 'V',
+}
+
+TEST {
+    { path = 'a.lua', content = [[
+X = 1
+X = true
+]], },
+    { path = 'b.lua', content = [[
+print(<?X?>)
+]], },
+    infer = 'integer',
+}
+
+TEST {
+    { path = 'a.lua', content = [[
+---@meta
+X = 1
+X = true
+]], },
+    { path = 'b.lua', content = [[
+print(<?X?>)
+]], },
+    infer = 'boolean|integer',
+}
+
+TEST {
+    { path = 'a.lua', content = [[
+return 1337, "string", true
+]], },
+    { path = 'b.lua', content = [[
+local <?a?>, b, c = require 'a
+]], },
+    infer = 'integer',
+}
+
+TEST {
+    { path = 'a.lua', content = [[
+return 1337, "string", true
+]], },
+    { path = 'b.lua', content = [[
+local a, <?b?>, c = require 'a
+]], },
+    infer = 'unknown',
+}
+
+TEST {
+    { path = 'a.lua', content = [[
+return 1337, "string", true
+]], },
+    { path = 'b.lua', content = [[
+local a, b, <?c?> = 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 7376ccd80..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,11 +74,12 @@ function TEST(datas)
             sourceUri = uri
         end
         files.setText(uri, newScript)
+        files.compileState(uri)
     end
 
     local _ <close> = function ()
         for _, info in ipairs(datas) do
-            files.remove(furi.encode(info.path))
+            files.remove(furi.encode(TESTROOT .. info.path))
         end
     end
 
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[#<?obj?>+1] = {}
 ]]
 
+TEST [[
+self = {
+    <!results!> = {}
+}
+self[self.<?results?>] = lbl
+]]
+
 TEST [[
 self = {
     results = {
diff --git a/test/definition/luadoc.lua b/test/definition/luadoc.lua
index ae2c8e61e..b877f5cd5 100644
--- a/test/definition/luadoc.lua
+++ b/test/definition/luadoc.lua
@@ -975,3 +975,15 @@ print(x.<?a?>)
 ]]
 
 config.set(nil, 'Lua.type.castNumberToInteger', true)
+
+TEST [[
+---@class <!A!>
+
+---@class <!<?A?>!>
+]]
+
+TEST [[
+---@alias <!A!> number
+
+---@alias <!<?A?>!> 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 = <!x or 0 + 1!>
+]]
+
+TEST [[
+local x, y
+x = <!x + y or 0!>
+]]
+
+TEST [[
+local x, y, z
+x = x and y or '' .. z
+]]
+
+TEST [[
+local x
+x = x or -1
+]]
+
+TEST [[
+local x
+x = x or (0 + 1)
+]]
+
+TEST [[
+local x, y
+x = (x + y) or 0
+]]
diff --git a/test/diagnostics/assign-type-mismatch.lua b/test/diagnostics/assign-type-mismatch.lua
new file mode 100644
index 000000000..d46325633
--- /dev/null
+++ b/test/diagnostics/assign-type-mismatch.lua
@@ -0,0 +1,474 @@
+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
+
+<!t.x!> = true
+]]
+
+TEST [[
+---@class A
+---@field x integer
+
+---@type A
+local t
+
+---@type boolean
+local y
+
+<!t.x!> = y
+]]
+
+TEST [[
+---@class A
+local m
+
+m.x = 1
+
+---@type A
+local t
+
+<!t.x!> = true
+]]
+
+TEST [[
+---@class A
+local m
+
+---@type integer
+m.x = 1
+
+<!m.x!> = true
+]]
+
+TEST [[
+---@class A
+local mt
+
+---@type integer
+mt.x = 1
+
+function mt:init()
+    <!self.x!> = true
+end
+]]
+
+TEST [[
+---@class A
+---@field x integer
+
+---@type A
+local t = {
+    <!x!> = true
+}
+]]
+
+TEST [[
+---@type boolean[]
+local t = {}
+
+t[5] = nil
+]]
+
+TEST [[
+---@type table<string, true>
+local t = {}
+
+t['x'] = nil
+]]
+
+TEST [[
+local t = { true }
+
+t[1] = nil
+]]
+
+TEST [[
+---@class A
+local t = {
+    x = 1
+}
+
+<!t.x!> = 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
+
+<!m.x!> = {}
+]]
+
+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
+
+<?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
+
+<?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 <!x!> = '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 <!b!> = 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 <!b!> = 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 <!test!> = 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 = {
+    <!true!>,
+    <![10]!> = 's',
+    <!xx!> = 1,
+}
+]]
+
+TEST [[
+---@type boolean[]
+local t = { <!1!>, <!2!>, <!3!> }
+]]
+
+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 = {
+    <![enum.a]!> = 1,
+    <![enum.b]!> = 2,
+    <![3]!> = 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 = {
+    <!3!>,
+}
+]]
+
+TEST [[
+---@type (string|boolean)[]
+local arr2 = {
+    <!3!>, -- 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()
+    <!coroutine.yield!>()
+end
+]]
+
+TEST [[
+---@async
+function F()
+    coroutine.yield()
+end
+]]
+
+TEST [[
+---@type async fun()
+local f
+
+function F()
+    <!f!>()
+end
+]]
+
+TEST [[
+---@type async fun()
+local f
+
+---@async
+function F()
+    f()
+end
+]]
+
+TEST [[
+local function f(cb)
+    cb()
+end
+
+return function()
+    <!f>(function () ---@async
+        return nil
+    end)
+end
+]]
+
+TEST [[
+local function f(cb)
+    pcall(cb)
+end
+
+return function()
+    <!f!>(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()
+    <!f!>(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
+
+<!x!> = true
+]]
+
+TEST [[
+---@type integer
+local x
+
+<!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
+
+<?x?> = nil
+]]
+
+TEST [[
+---@type string?
+local x
+
+x = nil
+]]
+
+TEST [[
+---@type table
+local x
+
+<!x!> = nil
+]]
+
+TEST [[
+local x
+
+x = nil
+]]
+
+TEST [[
+---@type integer
+local x
+
+---@type number
+<!x!> = f()
+]]
+
+TEST [[
+---@type number
+local x
+
+---@type integer
+x = f()
+]]
+
+TEST [[
+---@type number|boolean
+local x
+
+---@type string
+<!x!> = f()
+]]
+
+TEST [[
+---@type number|boolean
+local x
+
+---@type boolean
+x = f()
+]]
+
+TEST [[
+---@type number|boolean
+local x
+
+---@type boolean|string
+<!x!> = f()
+]]
+
+TEST [[
+---@type boolean
+local x
+
+if not x then
+    return
+end
+
+x = f()
+]]
+
+TEST [[
+---@type boolean
+local x
+
+---@type integer
+local y
+
+<!x!> = 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
+
+<!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
+
+<!t!> = 'xxx'
+]]
+
+TEST [[
+---@type 1|2
+local x
+
+x = 1
+x = 2
+<!x!> = 3
+]]
+
+TEST [[
+---@type 'x'|'y'
+local x
+
+x = 'x'
+x = 'y'
+<!x!> = 'z'
+]]
+
+TEST [[
+local t = {
+    x = 1,
+}
+
+local x
+t[x] = true
+]]
+
+TEST [[
+---@type table<string, string>
+local x
+
+---@type table<number, string>
+local y
+
+<!x!> = y
+]]
+
+TEST [[
+---@type table<string, string>
+local x
+
+---@type table<string, number>
+local y
+
+<!x!> = y
+]]
+
+TEST [[
+---@type table<string, string>
+local x
+
+---@type table<string, string>
+local y
+
+x = y
+]]
+
+TEST [[
+---@type { x: number, y: number }
+local t1
+
+---@type { x: number }
+local t2
+
+<!t1!> = t2
+]]
+
+TEST [[
+---@type { x: number, [integer]: number }
+local t1
+
+---@type { x: number }
+local t2
+
+<!t1!> = t2
+]]
+
+TEST [[
+local x
+
+if X then
+    x = 'A'
+elseif X then
+    x = 'B'
+else
+    x = 'C'
+end
+
+local y = x
+
+<!y!> = 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
+
+<!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 <!number!>
+]]
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 <!A : B!>
+---@class <!B : C!>
+---@class <!C : D!>
+---@class <!D : A!>
+]]
+
+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 _ <close> = <!1!>
+]]
+
+TEST [[
+local _ <close> = <!''!>
+]]
+
+TEST [[
+local c <close> = <!(function () return 1 end)()!>
+]]
+
+TEST [[
+---@type unknown
+local t
+
+local _ <close> = 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
+    <!print()
+    print()!>
+end
+]]
diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua
deleted file mode 100644
index b91380b83..000000000
--- a/test/diagnostics/common.lua
+++ /dev/null
@@ -1,2233 +0,0 @@
-local config = require 'config'
-local util   = require 'utility'
-
-local disables = config.get(nil, 'Lua.diagnostics.disable')
-
-TEST [[
-local <!x!>
-]]
-
-TEST [[
-local y
-local x <close> = y
-]]
-
-TEST [[
-local function x()
-end
-x()
-]]
-
-TEST [[
-return function (x)
-    x.a = 1
-end
-]]
-
-TEST [[
-local <!t!> = {}
-<!t!>.a = 1
-]]
-
-TEST [[
-local <!function <!x!>()
-end!>
-]]
-
-
-TEST [[
-local <!x!> = <!function () end!>
-]]
-
-TEST [[
-local <!x!>
-<!x!> = <!function () end!>
-]]
-
-TEST [[
-local <!function x()
-end!>
-local <!function <!y!>()
-    x()
-end!>
-]]
-
-TEST [[
-local print, _G
-print(<!x!>)
-print(<!log!>)
-print(<!X!>)
-print(<!Log!>)
-print(<!y!>)
-print(Z)
-print(_G)
-Z = 1
-]]
-
-TEST [[
-::<!LABEL!>::
-]]
-
-TEST [[
-<!    !>
-]]
-
-TEST [[
-
-<!    !>
-]]
-
-TEST [[
-X = 1<!  !>
-]]
-
-TEST [[
-X = [=[  
-    ]=]
-]]
-
-TEST [[
--- xxxx  
-]]
-
-TEST [[
--- [=[  
-   ]=]
-]]
-
-TEST [[
-local x
-print(x)
-local <!x!>
-print(x)
-]]
-
-TEST [[
-local x
-print(x)
-local <!x!>
-print(x)
-local <!x!>
-print(x)
-]]
-
-TEST [[
-local _
-print(_)
-local _
-print(_)
-local _ENV
-<!print!>(_ENV) -- 由于重定义了_ENV，因此print变为了未定义全局变量
-]]
-
-TEST [[
-local x
-return x, function (<!x!>)
-    return x
-end
-]]
-
-TEST [[
-print(1)
-_ENV = nil
-]]
-
-TEST [[
----@diagnostic disable: undefined-global
-_ENV = nil
-<!print!>(<!A!>) -- `print` and `A` should warning 
-]]
-
-TEST [[
----@diagnostic disable: undefined-global
-local _ENV = nil
-<!print!>(<!A!>) -- `print` and `A` should warning 
-]]
-
-TEST [[
-_ENV = {}
-print(A) -- no warning
-]]
-
-TEST [[
-local _ENV = {}
-print(A) -- no warning
-]]
-
-TEST [[
----@type iolib
-_ENV = {}
-<!print!>(stderr) -- `print` is warning but `stderr` is not
-]]
-
-TEST [[
----@type iolib
-local _ENV = {}
-<!print!>(stderr) -- `print` is warning but `stderr` is not
-]]
-
-TEST [[
-local _ENV = { print = print }
-print(1)
-]]
-
-util.arrayInsert(disables, 'undefined-env-child')
-TEST [[
-_ENV = nil
-<!GLOBAL!> = 1 --> _ENV.GLOBAL = 1
-]]
-
-TEST [[
-_ENV = nil
-local _ = <!print!> --> local _ = _ENV.print
-]]
-
-TEST [[
-_ENV = {}
-GLOBAL = 1 --> _ENV.GLOBAL = 1
-]]
-
-TEST [[
-_ENV = {}
-local _ = print --> local _ = _ENV.print
-]]
-
-TEST [[
-GLOBAL = 1
-_ENV = nil
-]]
-
-util.arrayRemove(disables, 'undefined-env-child')
-TEST [[
-<!print()
-('string')!>:sub(1, 1)
-]]
-
-TEST [[
-print()
-('string')
-]]
-
-TEST [[
-print
-{}
-{}
-]]
-
-TEST [[
-local x
-return x
-    : f(1)
-    : f(1)
-]]
-
-TEST [[
-return {
-    <!print
-    'string'!>
-}
-]]
-
-TEST [[
-return {
-    <!print
-    {
-        x = 1,
-    }!>
-}
-]]
-
-TEST [[
-print()
-'string'
-]]
-
-TEST [[
-print
-{
-    x = 1,
-}
-]]
-
-TEST [[
-local function x(a, b)
-    return a, b
-end
-x(1, 2, <!3!>)
-]]
-
-TEST [[
-local function x(a, b, ...)
-    return a, b, ...
-end
-x(1, 2, 3, 4, 5)
-]]
-
-TEST [[
----@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, <!3!>)
-]]
-
-TEST [[
-local m = {}
-function m:x(a, b)
-    return a, b
-end
-m.x(m, 2, 3, <!4!>)
-]]
-
-TEST [[
-local m = {}
-function m.x(a, b)
-    return a, b
-end
-m:x(1, <!2!>, <!3!>, <!4!>)
-]]
-
-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
-<!x(1)!>
-]]
-
-TEST [[
----@param a integer
----@param b integer
-local function x(a, b)
-    return a, b
-end
-<!x()!>
-]]
-
-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 _ <close> = function () end
-]]
-util.arrayRemove(disables, 'close-non-object')
-
-TEST [[
-local _ <close> = <!1!>
-]]
-
-TEST [[
-local _ <close> = <!''!>
-]]
-
-TEST [[
-local c <close> = <!(function () return 1 end)()!>
-]]
-
-util.arrayInsert(disables, 'unused-local')
-TEST [[
-local f = <!function () end!>
-]]
-
-TEST [[
-local f;f = <!function () end!>
-]]
-
-TEST [[
-local <!function f() end!>
-]]
-
-TEST [[
-local <!function f()
-    f()
-end!>
-]]
-
-
-TEST [[
-local <!function test()
-end!>
-
-local <!function foo ()
-end!>
-]]
-
-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 <!mt!> = {}
-function <!mt!>:f()
-end
-]]
-
-TEST [[
-local <!x!> = {}
-<!x!>.a = 1
-]]
-
-TEST [[
-local <!x!> = {}
-<!x!>['a'] = 1
-]]
-
-TEST [[
-local function f(<!self!>)
-    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, <!3!>, <!4!>)
-]]
-
-TEST [[
-local mt = {}
-function mt:f(a, b)
-    return a, b
-end
-mt.f(mt, 2, 3, <!4!>)
-]]
-
-
-TEST [[
-local mt = {}
-function mt.f(a, b)
-    return a, b
-end
-mt:f(1, <!2!>, <!3!>, <!4!>)
-]]
-
-TEST [[
-local mt = {}
-function mt:f(a, b)
-    return a, b
-end
-mt:f(1, 2, <!3!>, <!4!>)
-]]
-
-TEST [[
-local function f(a, b, ...)
-    return a, b, ...
-end
-f(1, 2, 3, 4)
-]]
-
-TEST [[
-local _ = next({}, 1, <!2!>)
-print(1, 2, 3, 4, 5)
-]]
-
-TEST [[
-local function f(callback)
-    callback(1, 2, 3)
-end
-f(function () end)
-]]
-
---TEST [[
---local realTostring = tostring
---tostring = function () end
---tostring(<!1!>)
---tostring = realTostring
---tostring(1)
---]]
-
-TEST [[
-<!aa!> = 1
-tostring = 1
-ROOT = 1
-_G.bb = 1
-]]
-
-TEST [[
-local f = load('')
-if f then
-    f(1, 2, 3)
-end
-]]
-
-TEST [[
-local _ = <!unpack!>
-]]
-
-TEST [[
-X = table[<!x!>]
-]]
-
-TEST [[
-return {
-    <!x!> = 1,
-    y = 2,
-    <!x!> = 3,
-}
-]]
-
-TEST [[
-return {
-    x = 1,
-    y = 2,
-}, {
-    x = 1,
-    y = 2,
-}
-]]
-
-TEST [[
-local m = {}
-function m.open()
-end
-
-m:open()
-]]
-
-TEST [[
-local m = {}
-function m:open()
-end
-
-m.open(m)
-]]
-
-TEST [[
-<!if true then
-end!>
-]]
-
-TEST [[
-<!if true then
-else
-end!>
-]]
-
-TEST [[
-if true then
-else
-    return
-end
-]]
-
-TEST [[
-while true do
-end
-]]
-
-TEST [[
-<!for _ = 1, 10 do
-end!>
-]]
-
-TEST [[
-<!for _ in pairs({}) do
-end!>
-]]
-
-TEST [[
-local _ = 1, <!2!>
-]]
-
-TEST [[
-_ = 1, <!2!>
-]]
-
-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 <!t!> = {}
-<!t!>[1] = 1
-]]
-
-TEST [[
-T1 = 1
-_ENV.T2 = 1
-_G.T3 = 1
-_ENV._G.T4 = 1
-_G._G._G.T5 = 1
-rawset(_G, 'T6', 1)
-rawset(_ENV, 'T7', 1)
-print(T1)
-print(T2)
-print(T3)
-print(T4)
-print(T5)
-print(T6)
-print(T7)
-]]
-
-TEST [[
-local x
-x = <!x or 0 + 1!>
-]]
-
-TEST [[
-local x, y
-x = <!x + y or 0!>
-]]
-
-TEST [[
-local x, y, z
-x = x and y or '' .. z
-]]
-
-TEST [[
-local x
-x = x or -1
-]]
-
-TEST [[
-local x
-x = x or (0 + 1)
-]]
-
-TEST [[
-local x, y
-x = (x + y) or 0
-]]
-
-TEST [[
-local t = {}
-t.a = 1
-t.a = 2
-return t
-]]
-
-TEST [[
-table.insert({}, 1, 2, <!3!>)
-]]
-
-TEST [[
-while true do
-    break
-    <!print()
-    print()!>
-end
-]]
-
-TEST [[
-local x, <!y!>, <!z!> = 1
-print(x, y, z)
-]]
-
-TEST [[
-local x, y, <!z!> = 1, 2
-print(x, y, z)
-]]
-
-TEST [[
-local x, y, z = print()
-print(x, y, z)
-]]
-
-TEST [[
-local x, y, z
-print(x, y, z)
-]]
-
-TEST [[
-local x, y, z
-x, <!y!>, <!z!> = 1
-print(x, y, z)
-]]
-
-TEST [[
-X, <!Y!>, <!Z!> = 1
-]]
-
-TEST [[
-T = {}
-T.x, <!T.y!>, <!T.z!> = 1
-]]
-
-TEST [[
-T = {}
-T['x'], <!T['y']!>, <!T['z']!> = 1
-]]
-
---TEST [[
------@class <!Class!>
------@class <!Class!>
---]]
-
-TEST [[
----@alias <!A!> integer
----@alias <!A!> integer
-]]
-
-TEST [[
----@class A : <!B!>
-]]
-
-TEST [[
----@class <!A : B!>
----@class <!B : C!>
----@class <!C : D!>
----@class <!D : A!>
-]]
-
-TEST [[
----@class A : B
----@class B : C
----@class C : D
----@class D
-]]
-
-TEST [[
----@type <!A!>
-]]
-
-TEST [[
----@class A
----@type A|<!B!>|<!C!>
-]]
-
-TEST [[
----@class AAA
----@alias B AAA
-
----@type B
-]]
-
-TEST [[
----@alias B <!AAA!>
-]]
-
-TEST [[
----@class A
----@class B
----@alias <!A!> B
-]]
-
-TEST [[
----@param <!x!> <!Class!>
-]]
-
-TEST [[
----@class Class
----@param <!y!> Class
-local function f(x)
-    return x
-end
-f()
-]]
-
-TEST [[
----@class Class
----@param <!y!> Class
-function F(x)
-    return x
-end
-F()
-]]
-
-TEST [[
----@class Class
----@param <!x!> Class
----@param y Class
----@param <!x!> Class
-local function f(x, y)
-    return x, y
-end
-
-local _
-f(_, _)
-]]
-
-TEST [[
----@field <!x Class!>
----@class Class
-]]
-
-TEST [[
----@class Class
-
----@field <!x Class!>
-]]
-
-TEST [[
----@class Class
----
----@field x Class
-]]
-
-TEST [[
----@class Class
----@field <!x!> Class
----@field <!x!> Class
-]]
-
-TEST [[
----@class Class : any
-]]
-
-TEST [[
----@type fun(a: integer)
-local f
-f(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.<!field3!> + 1)
-print(v:method1())
-print(v.method2())
-print(v:<!method3!>())
-
----@type Bar
-local v2
-print(v2.field1 + 1)
-print(v2.field2 + 1)
-print(v2.<!field3!> + 1)
-print(v2.field4 + 1)
-print(v2:method1())
-print(v2.method2())
-print(v2:<!method3!>())
-
-local v3 = {}
-print(v3.abc)
-
----@class Bar2
-local mt3
-function mt3:method() return 1 end
-print(mt3:method())
-]]
-
--- checkUndefinedField 通过type找到class
-TEST [[
----@class Foo
-local Foo
-function Foo:method1() end
-
----@type Foo
-local v
-v:method1()
-v:<!method2!>() -- doc.class.name
-]]
-
--- checkUndefinedField 通过type找到class，涉及到 class 继承版
-TEST [[
----@class Foo
-local Foo
-function Foo:method1() end
----@class Bar: Foo
-local Bar
-function Bar:method3() end
-
----@type Bar
-local v
-v:method1()
-v:<!method2!>() -- doc.class.name
-v:method3()
-]]
-
--- checkUndefinedField 类名和类变量同名，类变量被直接使用
-TEST [[
----@class Foo
-local Foo
-function Foo:method1() end
-Foo:<!method2!>() -- doc.class
-Foo:<!method2!>() -- doc.class
-]]
-
--- checkUndefinedField 没有@class的不检测
-TEST [[
-local Foo
-function Foo:method1()
-    return Foo:method2() -- table
-end
-]]
-
--- checkUndefinedField 类名和类变量不同名，类变量被直接使用、使用self
-TEST [[
----@class Foo
-local mt
-function mt:method1()
-    mt.<!method2!>() -- doc.class
-    self:method1()
-    return self.<!method2!>() -- doc.class.name
-end
-]]
-
--- checkUndefinedField 当会推导成多个class类型时
-TEST [[
----@class Foo
-local mt
-function mt:method1() end
-
----@class Bar
-local mt2
-function mt2:method2() end
-
----@type Foo
-local v
----@type Bar
-local v2
-<!v2!> = v
-v2:method1()
-v2:<!method2!>()
-]]
-
-TEST [[
----@type table
-T1 = {}
-print(T1.f1)
----@type tablelib
-T2 = {}
-print(T2.<!f2!>)
-]]
---]==]
-TEST [[
----@overload fun(...)
-local function f() end
-
-f(1)
-]]
-
-TEST [[
-for i = <!10, 1!> do
-    print(i)
-end
-]]
-
-TEST [[
-for i = <!10, 1, 5!> do
-    print(i)
-end
-]]
-
-TEST [[
-for i = <!100, 10, 1!> do
-    print(i)
-end
-]]
-
-TEST [[
-for i = <!1, -10!> do
-    print(i)
-end
-]]
-
-TEST [[
-for i = 1, 1 do
-    print(i)
-end
-]]
-
-TEST [[
-local m = {}
-
-function <!m:fff!>()
-end
-
-function <!m:fff!>()
-end
-
-return m
-]]
-
-TEST [[
-local m = {}
-
-function <!m:fff!>()
-end
-
-do
-    function <!m:fff!>()
-    end
-end
-
-return m
-]]
-
-TEST [[
-local m = {}
-
-m.x = true
-m.x = false
-
-return m
-]]
-
-TEST [[
-local m = {}
-
-m.x = io.open('')
-m.x = nil
-
-return m
-]]
-
-TEST [[
----@meta
-
----@class A
----@field a boolean
-
----@return A
-local function f() end
-
-local r = f()
-r.x = 1
-
-return r.x
-]]
-
-TEST [[
----@diagnostic disable-next-line
-x = 1
-]]
-
-TEST [[
----@diagnostic disable-next-line: lowercase-global
-x = 1
-]]
-
-TEST [[
----@diagnostic disable-next-line: unused-local
-<!x!> = 1
-]]
-
-TEST [[
----@diagnostic disable
-x = 1
-]]
-
-TEST [[
----@diagnostic disable
----@diagnostic enable
-<!x!> = 1
-]]
-
-TEST [[
----@diagnostic disable
----@diagnostic disable
----@diagnostic enable
-x = 1
-]]
-
-TEST [[
----@diagnostic disable-next-line: <!xxx!>
-]]
-
-TEST [[
-local mt = {}
-
-function mt:a(x)
-    return self, x
-end
-
-function mt:b(y)
-    self:a(1):b(2)
-    return y
-end
-
-return mt
-]]
-
-TEST [[
-local function each()
-    return function ()
-    end
-end
-
-for x in each() do
-    print(x)
-end
-]]
-
-TEST [[
----@type string
-local s
-
-print(s:upper())
-]]
-
-TEST [[
-local t = ().
-return t
-]]
-
-TEST [[
-return {
-    [1] = 1,
-    ['1'] = 1,
-}
-]]
-
-TEST [[
-return {
-    [print()] = 1,
-    [print()] = 1,
-}
-]]
-
-TEST [[
----@type { x: number, y: number}
----| "'resume'"
-]]
-
-TEST [[
-return {
-    1, <!2!>, 3,
-    [<!2!>] = 4,
-}
-]]
-
-TEST [[
---- @class Emit
---- @field on fun(eventName: string, cb: function)
---- @field on fun(eventName: '"died"', cb: fun(i: integer))
---- @field on fun(eventName: '"won"', cb: fun(s: string))
-local emit = {}
-]]
-
-TEST [[
---- @class Emit
---- @field on fun(eventName: string, cb: function)
---- @field <!on!> fun(eventName: '"died"', cb: fun(i: integer))
---- @field on fun(eventName: '"won"', cb: fun(s: string))
---- @field <!on!> fun(eventName: '"died"', cb: fun(i: integer))
-local emit = {}
-]]
-
--- redundant-return
-TEST [[
-local function f()
-    <!return!>
-end
-f()
-]]
-
-TEST [[
-local function f()
-    return nil
-end
-f()
-]]
-
-TEST [[
-local function f()
-    local function x()
-        <!return!>
-    end
-    x()
-    return true
-end
-f()
-]]
-
-TEST [[
-local function f()
-    local function x()
-        return true
-    end
-    return x()
-end
-f()
-]]
-
-TEST [[
----@type file*
-local f
-local _ = f:read '*a'
-local _ = f:read('*a')
-]]
-
-TEST [[
-function F()
-    <!coroutine.yield!>()
-end
-]]
-
-TEST [[
----@async
-function F()
-    coroutine.yield()
-end
-]]
-
-TEST [[
----@type async fun()
-local f
-
-function F()
-    <!f!>()
-end
-]]
-
-TEST [[
----@type async fun()
-local f
-
----@async
-function F()
-    f()
-end
-]]
-
-TEST [[
-local function f(cb)
-    cb()
-end
-
-return function()
-    <!f>(function () ---@async
-        return nil
-    end)
-end
-]]
-
-TEST [[
-local function f(cb)
-    pcall(cb)
-end
-
-return function()
-    <!f!>(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 [[
----@nodiscard
-local function f()
-    return 1
-end
-
-<!f()!>
-]]
-
-TEST [[
----@nodiscard
-local function f()
-    return 1
-end
-
-X = f()
-]]
-
-config.get(nil, 'Lua.diagnostics.neededFileStatus')['not-yieldable'] = 'Any'
-TEST [[
----@param cb fun()
-local function f(cb)
-    return cb
-end
-
----@async
-local function af()
-    return nil
-end
-
-f(<!af!>)
-]]
-
-TEST [[
----@param cb async fun()
-local function f(cb)
-    return cb
-end
-
----@async
-local function af()
-    return nil
-end
-
-f(af)
-]]
-
-TEST [[
-local function f(cb)
-    cb()
-end
-
-local function af()
-    <!f!>(function () ---@async
-        return nil
-    end)
-end
-
-return af
-]]
-
-TEST [[
-local function f(cb)
-    cb()
-end
-
----@async
-local function af()
-    f(function () ---@async
-        return nil
-    end)
-end
-
-return af
-]]
-
-TEST [[
-local _ = type(function () ---@async
-    return nil
-end)
-]]
-
-TEST [[
----@param ... number
-local function f(...)
-    return ...
-end
-
-return f
-]]
-
-TEST [[
----@type fun(...: string)
-]]
-
-TEST [[
----@type fun(xxx, yyy, ...): boolean
-]]
-
-TEST [[
-local <!x!>
-
-return {
-    x = 1,
-}
-]]
-
-TEST [[
----@class A #1
-]]
-
-TEST [[
----@class A 1
-]]
-
-TEST [[
-return ('1'):upper()
-]]
-
-TEST [[
-local value
-value = '1'
-value = value:upper()
-]]
-
-TEST [[
-T = {}
----@deprecated # comment
-T.x = 1
-
-print(<!T.x!>)
-]]
-
-TEST [[
-T = {}
-
----@deprecated
-function T:ff()
-end
-
-<!T:ff!>()
-]]
-
-TEST [[
----@type string?
-local x
-
-S = <!x!>: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 = <!x!>()
-]]
-
-TEST [[
----@type integer?
-local x
-
-T = {}
-T[<!x!>] = 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(<!f()!>)
-]]
-
-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 <!x!> 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 [<!A!>] 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
-
-<!t.init()!>
-]]
-
-TEST [[
----@meta
-
-return function f(x, y, z) end
-]]
-
-util.arrayInsert(disables, 'redundant-return')
-TEST [[
----@return number
-function F()
-    <!return!>
-end
-]]
-
-TEST [[
----@return number, number
-function F()
-    <!return!> 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, <!1!>
-end
-]]
-
-TEST [[
----@return number, number?
-function F()
-    return 1, 1, <!1!>, <!2!>, <!3!>
-end
-]]
-
-TEST [[
----@meta
-
----@return number, number
-local function r2() end
-
----@return number, number?
-function F()
-    return 1, <!r2()!>
-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 <!xxx!>: A
-]]
-
-config.add(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*')
-
-TEST [[
-local <!xx!>
-local ll_1
-local ll_2
-local <!ll!>
-]]
-
-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
-<!return true!>
-]]
-
-TEST [[
----@diagnostic disable: undefined-global
-
-function X()
-    if X then
-        return false
-    elseif X then
-        return false
-    else
-        return false
-    end
-    <!return true!>
-end
-]]
-
-TEST [[
-while true do
-end
-
-<!print(1)!>
-]]
-
-TEST [[
-while true do
-end
-
-<!print(1)!>
-]]
-
-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 _ <close> = t
-]]
-
-TEST [[
----@meta
----@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)
-]]
-
-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.<!x!>)
-]]
-
-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.<!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.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.<!_id!>)
-
----@class B: A
-local t2
-
-print(t2.<!_id!>)
-]]
-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.<!_id!>)
-
----@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
----@class A
----@field private x number
-local mt = {}
-
-function mt:init()
-    ---@type A
-    local obj = {}
-
-    obj.x = 1
-end
-]]
-
-TEST [[
----@diagnostic disable: unused-local
----@class A
----@field private x number
-local mt = {}
-
-mt.init = function ()
-    ---@type A
-    local obj = {}
-
-    obj.x = 1
-end
-]]
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 = <!10, 1!> do
+    print(i)
+end
+]]
+
+TEST [[
+for i = <!10, 1, 5!> do
+    print(i)
+end
+]]
+
+TEST [[
+for i = <!100, 10, 1!> do
+    print(i)
+end
+]]
+
+TEST [[
+for i = <!1, -10!> 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 _ = <!unpack!>
+]]
+
+TEST [[
+T = {}
+---@deprecated # comment
+T.x = 1
+
+print(<!T.x!>)
+]]
+
+TEST [[
+T = {}
+
+---@deprecated
+function T:ff()
+end
+
+<!T:ff!>()
+]]
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
+
+<!f()!>
+]]
+
+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 <!x Class!>
+---@class Class
+]]
+
+TEST [[
+---@class Class
+
+---@field <!x Class!>
+]]
+
+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 <!A!> integer
+---@alias <!A!> integer
+]]
+
+TEST [[
+---@class A
+---@class B
+---@alias <!A!> 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 <!x!> Class
+---@field <!x!> 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 <!on!> fun(eventName: '"died"', cb: fun(i: integer))
+--- @field on fun(eventName: '"won"', cb: fun(s: string))
+--- @field <!on!> fun(eventName: '"died"', cb: fun(i: integer))
+local emit = {}
+]]
+
+TEST [[
+---@class A
+
+---@class B
+---@field [integer] A
+---@field [A] true
+]]
+
+TEST [[
+---@class A
+
+---@class B
+---@field [<!A!>] A
+---@field [<!A!>] 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 <!x!> Class
+---@param y Class
+---@param <!x!> 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 {
+    <!x!> = 1,
+    y = 2,
+    <!x!> = 3,
+}
+]]
+
+TEST [[
+return {
+    x = 1,
+    y = 2,
+}, {
+    x = 1,
+    y = 2,
+}
+]]
+
+TEST [[
+return {
+    1, <!2!>, 3,
+    [<!2!>] = 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 <!m:fff!>()
+end
+
+function <!m:fff!>()
+end
+
+return m
+]]
+
+TEST [[
+local m = {}
+
+function <!m:fff!>()
+end
+
+do
+    function <!m:fff!>()
+    end
+end
+
+return m
+]]
+
+TEST [[
+local m = {}
+
+m.x = true
+m.x = false
+
+return m
+]]
+
+TEST [[
+local m = {}
+
+m.x = io.open('')
+m.x = nil
+
+return m
+]]
+
+TEST [[
+---@class A
+X = {}
+
+function <!X.f!>() end
+
+function <!X.f!>() 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 [[
+<!if true then
+end!>
+]]
+
+TEST [[
+<!if true then
+else
+end!>
+]]
+
+TEST [[
+if true then
+else
+    return
+end
+]]
+
+TEST [[
+while true do
+end
+]]
+
+TEST [[
+<!for _ = 1, 10 do
+end!>
+]]
+
+TEST [[
+<!for _ in pairs({}) do
+end!>
+]]
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
+<!Y!> = "global"
+<!z!> = "global"
+]]
+
+TEST [[
+local function test1()
+    print()
+end
+
+function <!Test2!>()
+    print()
+end
+]]
+
+TEST [[
+local function closure1()
+    local elem1 = 1
+    <!elem2!> = 2
+end
+
+function <!Closure2!>()
+    local elem1 = 1
+    <!elem2!> = 2
+end
+]]
+
+-- add elements to exemption list
+config.set(nil, 'Lua.diagnostics.globals',
+{
+    'GLOBAL1',
+    'GLOBAL2',
+    'GLOBAL_CLOSURE'
+})
+
+TEST [[
+GLOBAL1 = "allowed"
+<!global2!> = "not allowed"
+<!GLOBAL3!> = "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
+    <!elem2!> = 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
+<!print!>(_ENV) -- 由于重定义了_ENV，因此print变为了未定义全局变量
+]]
+
+TEST [[
+_ENV = nil
+<!print!>(<!A!>) -- `print` and `A` should warning 
+]]
+
+TEST [[
+local _ENV = nil
+<!print!>(<!A!>) -- `print` and `A` should warning 
+]]
+
+TEST [[
+_ENV = {}
+print(A) -- no warning
+]]
+
+TEST [[
+local _ENV = {}
+print(A) -- no warning
+]]
+
+TEST [[
+_ENV = nil
+<!GLOBAL!> = 1 --> _ENV.GLOBAL = 1
+]]
+
+TEST [[
+_ENV = nil
+local _ = <!print!> --> 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, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+---@param p1 any
+function FGPP2_(<!p0!>, 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, <!1!>
+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 <!0!>
+end
+
+---comment
+---@return integer
+function FGPR3(<!p!>)
+  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, <!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)
+]]
+
+-- 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, <!1!>
+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 <!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/init.lua b/test/diagnostics/init.lua
index 2d2374a5e..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(TESTURI, newScript)
     files.open(TESTURI)
     local origins = {}
+    local filteds = {}
     local results = {}
     core(TESTURI, false, function (result)
-        results[#results+1] = { result.start, result.finish }
+        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)
 
@@ -47,7 +58,71 @@ function TEST(script, ...)
     end
 
     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.<!yy!> = 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.<!yy!> = 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.<!y!> = 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
+    <!y!> = 2, -- Warning
+}
+
+m.x = 1 -- OK
+m.<!y!> = 2 -- Warning
+
+function m:init() -- OK
+    self.x = 1 -- OK
+    self.<!y!> = 2 -- Warning
+    function self:<!xx!>() -- 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.<!x!>)
+]]
+
+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.<!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.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.<!_id!>)
+
+---@class B: A
+local t2
+
+print(t2.<!_id!>)
+]]
+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.<!_id!>)
+
+---@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 [[
+<!aa!> = 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
+<!x!> = 1
+]]
+
+TEST [[
+---@diagnostic disable
+x = 1
+]]
+
+TEST [[
+---@diagnostic disable
+---@diagnostic enable
+<!x!> = 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 = <!{
+    x = 1,
+}!>
+]]
+
+TEST [[
+---@diagnostic disable: unused-local
+---@class A
+---@field x number
+---@field y? number
+---@field z number
+
+---@type A
+local t = <!{
+    x = 1,
+    y = 2,
+}!>
+]]
+
+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 <!{
+    x = 1,
+}!>
+]]
+
+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,
+}!>
+]]
+
+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 = <!{
+    z = 1,
+}!>
+]]
+
+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 [[
+<!function FG0()
+end!>
+
+---comment
+function FG1()
+end
+]]
+
+TEST [[
+function FGP0(<!p!>)
+  print(p)
+end
+
+---comment
+function FGP1(<!p!>)
+  print(p)
+end
+
+---comment
+---@param p any
+function FGP2(p)
+  print(p)
+end
+]]
+
+TEST [[
+function FGPP0(<!p0!>, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+function FGPP1(<!p0!>, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+---@param p0 any
+function FGPP2(p0, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+---@param p0 any
+---@param p1 any
+function FGPP3(p0, p1)
+  print(p0, p1)
+end
+]]
+
+TEST [[
+function FGR0()
+  return <!0!>
+end
+
+---comment
+function FGR1()
+  return <!0!>
+end
+
+---comment
+---@return integer
+function FGR2()
+  return 0
+end
+]]
+
+TEST [[
+function FGRR0()
+  return <!0!>, <!1!>
+end
+
+---comment
+function FGRR1()
+  return <!0!>, <!1!>
+end
+
+---comment
+---@return integer
+function FGRR2()
+  return 0, <!1!>
+end
+
+---comment
+---@return integer
+---@return integer
+function FGRR3()
+  return 0, 1
+end
+]]
+
+
+TEST [[
+function FGPR0(<!p!>)
+  print(p)
+  return <!0!>
+end
+
+---comment
+function FGPR1(<!p!>)
+  print(p)
+  return <!0!>
+end
+
+---comment
+---@param p any
+function FGPR2(p)
+  print(p)
+  return <!0!>
+end
+
+---comment
+---@return integer
+function FGPR3(<!p!>)
+  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 <!function fl0()
+end!>
+  
+---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(<!p!>)
+  print(p)
+end
+
+---comment
+local function flp1(<!p!>)
+  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(<!p0!>, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+local function flpp1(<!p0!>, <!p1!>)
+  print(p0, p1)
+end
+
+---comment
+---@param p0 any
+local function flpp2(p0, <!p1!>)
+  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 <!0!>
+end
+
+---comment
+local function flr1()
+  return <!0!>
+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 <!0!>, <!1!>
+end
+
+---comment
+local function flrr1()
+  return <!0!>, <!1!>
+end
+
+---comment
+---@return integer
+local function flrr2()
+  return 0, <!1!>
+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(<!p!>)
+  print(p)
+  return <!0!>
+end
+
+---comment
+local function flpr1(<!p!>)
+  print(p)
+  return <!0!>
+end
+
+---comment
+---@param p any
+local function flpr2(p)
+  print(p)
+  return <!0!>
+end
+
+---comment
+---@return integer
+local function flpr3(<!p!>)
+  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
+<!x(1)!>
+]]
+
+TEST [[
+---@param a integer
+---@param b integer
+local function x(a, b)
+    return a, b
+end
+<!x()!>
+]]
+
+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
+
+<!t.init()!>
+]]
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()
+    <!return!>
+end
+]]
+
+TEST [[
+---@return number
+function F()
+    <!return!>
+end
+]]
+
+TEST [[
+---@return number, number
+function F()
+    <!return!> 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 = <!x!>:upper()
+]]
+
+TEST [[
+---@type string?
+local x
+
+S = <!x!>: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 = <!x!>()
+]]
+
+TEST [[
+---@type integer?
+local x
+
+T = {}
+T[<!x!>] = 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 {
+    <!print
+    'string'!>
+}
+]]
+
+TEST [[
+return {
+    <!print
+    {
+        x = 1,
+    }!>
+}
+]]
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 [[
+<!print()
+('string')!>: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(<!af!>)
+]]
+
+TEST [[
+---@param cb async fun()
+local function f(cb)
+    return cb
+end
+
+---@async
+local function af()
+    return nil
+end
+
+f(af)
+]]
+
+TEST [[
+local function f(cb)
+    cb()
+end
+
+---@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(<!true!>)
+]]
+
+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(<!t!>)
+f(t.x)
+f(1)
+f(<!3!>)
+]]
+
+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(<!{ h = 1 }!>)
+]]
+
+TEST [[
+---@enum(key) A
+local t = {
+    x = 1,
+    ['y'] = 2,
+}
+
+---@param x A
+local function f(x)
+end
+
+f('x')
+f('y')
+f(<!'z'!>)
+]]
+
+TEST [[
+---@generic T: string | boolean | table
+---@param x T
+---@return T
+local function f(x)
+    return x
+end
+
+f(<!1!>)
+]]
+
+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(<!opts!>)  -- 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(<!true!>)
+Class<!:!>staticCreator() -- 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(<!1!>)
+]]
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 <!x!>
+print(x)
+]]
+
+TEST [[
+local x
+print(x)
+local <!x!>
+print(x)
+local <!x!>
+print(x)
+]]
+
+TEST [[
+local x
+return x, function (<!x!>)
+    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, <!3!>)
+]]
+
+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, <!3!>)
+]]
+
+TEST [[
+local m = {}
+function m:x(a, b)
+    return a, b
+end
+m.x(m, 2, 3, <!4!>)
+]]
+
+TEST [[
+local m = {}
+function m.x(a, b)
+    return a, b
+end
+m:x(1, <!2!>, <!3!>, <!4!>)
+]]
+
+TEST [[
+local m = {}
+function m.x()
+end
+m:x()
+]]
+
+TEST [[
+local function f(a, b)
+    return a, b
+end
+f(1, 2, <!3!>, <!4!>)
+]]
+
+TEST [[
+local mt = {}
+function mt:f(a, b)
+    return a, b
+end
+mt.f(mt, 2, 3, <!4!>)
+]]
+
+TEST [[
+local mt = {}
+function mt.f(a, b)
+    return a, b
+end
+mt:f(1, <!2!>, <!3!>, <!4!>)
+]]
+
+TEST [[
+local mt = {}
+function mt:f(a, b)
+    return a, b
+end
+mt:f(1, 2, <!3!>, <!4!>)
+]]
+
+TEST [[
+local function f(a, b, ...)
+    return a, b, ...
+end
+f(1, 2, 3, 4)
+]]
+
+TEST [[
+local _ = next({}, 1, <!2!>)
+print(1, 2, 3, 4, 5)
+]]
+
+TEST [[
+local function f(callback)
+    callback(1, 2, 3)
+end
+f(function () end)
+]]
+
+--TEST [[
+--local realTostring = tostring
+--tostring = function () end
+--tostring(<!1!>)
+--tostring = realTostring
+--tostring(1)
+--]]
+
+TEST [[
+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, <!3!>)
+]]
+
+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(<!f()!>)
+]]
+
+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, <!true!>
+end
+]]
+
+TEST [[
+---@return number, number?
+function F()
+    return 1, 1, <!1!>
+end
+]]
+
+TEST [[
+---@return number, number?
+function F()
+    return 1, 1, <!1!>, <!2!>, <!3!>
+end
+]]
+
+TEST [[
+---@meta
+
+---@return number, number
+local function r2() end
+
+---@return number, number?
+function F()
+    return 1, <!r2()!>
+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()
+    <!return!>
+end
+f()
+]]
+
+TEST [[
+local function f()
+    return nil
+end
+f()
+]]
+
+TEST [[
+local function f()
+    local function x()
+        <!return!>
+    end
+    x()
+    return true
+end
+f()
+]]
+
+TEST [[
+local function f()
+    local function x()
+        return true
+    end
+    return x()
+end
+f()
+]]
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, <!2!>
+]]
+
+TEST [[
+_ = 1, <!2!>
+]]
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 <!true!>
+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 <!f()!>
+end
+]]
+
+TEST [[
+---@return boolean, number
+local function f()
+    return true, 1
+end
+
+---@return number, boolean
+function F()
+    return <!f()!>
+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, <!f()!>
+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 <!true!>
+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 908920b96..000000000
--- a/test/diagnostics/type-check.lua
+++ /dev/null
@@ -1,1124 +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.add(nil, 'Lua.diagnostics.disable', 'redundant-return')
-config.set(nil, 'Lua.type.castNumberToInteger', false)
-
-TEST [[
-local x = 0
-
-<!x!> = true
-]]
-
-TEST [[
----@type integer
-local x
-
-<!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
-
-<?x?> = nil
-]]
-
-TEST [[
----@type string?
-local x
-
-x = nil
-]]
-
-TEST [[
----@type table
-local x
-
-<!x!> = nil
-]]
-
-TEST [[
-local x
-
-x = nil
-]]
-
-TEST [[
----@type integer
-local x
-
----@type number
-<!x!> = f()
-]]
-
-TEST [[
----@type number
-local x
-
----@type integer
-x = f()
-]]
-
-TEST [[
----@type number|boolean
-local x
-
----@type string
-<!x!> = f()
-]]
-
-TEST [[
----@type number|boolean
-local x
-
----@type boolean
-x = f()
-]]
-
-TEST [[
----@type number|boolean
-local x
-
----@type boolean|string
-<!x!> = f()
-]]
-
-TEST [[
----@type boolean
-local x
-
-if not x then
-    return
-end
-
-x = f()
-]]
-
-TEST [[
----@type boolean
-local x
-
----@type integer
-local y
-
-<!x!> = 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
-
-<!t.x!> = true
-]]
-
-TEST [[
----@class A
----@field x integer
-
----@type A
-local t
-
----@type boolean
-local y
-
-<!t.x!> = y
-]]
-
-TEST [[
----@class A
-local m
-
-m.x = 1
-
----@type A
-local t
-
-<!t.x!> = true
-]]
-
-TEST [[
----@class A
-local m
-
----@type integer
-m.x = 1
-
-<!m.x!> = true
-]]
-
-TEST [[
----@class A
-local mt
-
----@type integer
-mt.x = 1
-
-function mt:init()
-    <!self.x!> = true
-end
-]]
-
-TEST [[
----@class A
----@field x integer
-
----@type A
-local t = {
-    <!x!> = true
-}
-]]
-
-TEST [[
----@type boolean[]
-local t = {}
-
-t[5] = nil
-]]
-
-TEST [[
----@type table<string, true>
-local t = {}
-
-t['x'] = nil
-]]
-
-TEST [[
-local t = { true }
-
-t[1] = nil
-]]
-
-TEST [[
----@class A
-local t = {
-    x = 1
-}
-
-<!t.x!> = 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
-
-<!m.x!> = {}
-]]
-
-TEST [[
----@param x number
-local function f(x) end
-
-f(<!true!>)
-]]
-
-TEST [[
----@type integer
-local x
-
-x = 1.0
-]]
-
-TEST [[
----@type integer
-local x
-
-<!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 <!number!>
-]]
-
-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 = <!x!>: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
-
-<?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
-
-<?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 <!x!> = 'aaa'
-]]
-
-TEST [[
----@return number
-function F()
-    return <!true!>
-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 <!f()!>
-end
-]]
-
-TEST [[
----@return boolean, number
-local function f()
-    return true, 1
-end
-
----@return number, boolean
-function F()
-    return <!f()!>
-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, <!f()!>
-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 <!b!> = 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 <!b!> = 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
-
-<!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
-<!x!> = 3
-]]
-
-TEST [[
----@type 'x'|'y'
-local x
-
-x = 'x'
-x = 'y'
-<!x!> = 'z'
-]]
-
-TEST [[
----@enum A
-local t = {
-    x = 1,
-    y = 2,
-}
-
----@param x A
-local function f(x)
-end
-
-f(<!t!>)
-f(t.x)
-f(1)
-f(<!3!>)
-]]
-
-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(<!{ h = 1 }!>)
-]]
-
-TEST [[
-local t = {
-    x = 1,
-}
-
-local x
-t[x] = true
-]]
-
-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 [[
----@alias test boolean
-
----@type test
-local <!test!> = 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 [[
----@generic T: string | boolean | table
----@param x T
----@return T
-local function f(x)
-    return x
-end
-
-f(<!1!>)
-]]
-
-TEST [[
----@type table<string, string>
-local x
-
----@type table<number, string>
-local y
-
-<!x!> = y
-]]
-
-TEST [[
----@type table<string, string>
-local x
-
----@type table<string, number>
-local y
-
-<!x!> = y
-]]
-
-TEST [[
----@type table<string, string>
-local x
-
----@type table<string, string>
-local y
-
-x = y
-]]
-
-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(<!opts!>)  -- 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 [[
----@type {[1]: string, [10]: number, xx: boolean}
-local t = {
-    <!true!>,
-    <![10]!> = 's',
-    <!xx!> = 1,
-}
-]]
-
-TEST [[
----@type { x: number, y: number }
-local t1
-
----@type { x: number }
-local t2
-
-<!t1!> = t2
-]]
-
-TEST [[
----@type { x: number, [integer]: number }
-local t1
-
----@type { x: number }
-local t2
-
-<!t1!> = t2
-]]
-
-TEST [[
----@type boolean[]
-local t = { <!1!>, <!2!>, <!3!> }
-]]
-
-TEST [[
----@type boolean[]
-local t = { true, false, nil }
-]]
-
-TEST [[
----@type boolean|nil
-local x
-
----@type boolean[]
-local t = { true, false, x }
-]]
-
-TEST [[
----@type fun():number
-local function f()
-<!!>end
-]]
-
-TEST [[
----@type fun():number
-local function f()
-    <!return!>
-end
-]]
-
-TEST [[
----@type fun():number?
-local function f()
-end
-]]
-
-TEST [[
----@type fun():...
-local function f()
-end
-]]
-
-TEST [[
----@type fun():number
-local function f()
-    return 1, <!true!>
-end
-]]
-
-TEST [[
----@type fun():number
-local function f()
-    return <!true!>
-end
-]]
-
-TEST [[
----@enum Enum
-local t = {
-    x = 1,
-    y = 2,
-}
-
----@type Enum
-local y
-
----@type integer
-local x = y
-]]
-
-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 [[
----@type string|string[]|string[][]
-local t = {{'a'}}
-]]
-
-TEST [[
-local A = "Hello"
-local B = "World"
-
----@alias myLiteralAliases `A` | `B`
-
----@type myLiteralAliases
-local x = A
-]]
-
-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.remove(nil, 'Lua.diagnostics.disable', 'redundant-return')
-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, <!y!>, <!z!> = 1
+]]
+
+TEST [[
+local x, y, <!z!> = 1, 2
+]]
+
+TEST [[
+local x, y, z = print()
+]]
+
+TEST [[
+local x, y, z
+]]
+
+TEST [[
+local x, y, z
+x, <!y!>, <!z!> = 1
+]]
+
+TEST [[
+X, <!Y!>, <!Z!> = 1
+]]
+
+TEST [[
+T = {}
+T.x, <!T.y!>, <!T.z!> = 1
+]]
+
+TEST [[
+T = {}
+T['x'], <!T['y']!>, <!T['z']!> = 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 : <!B!>
+]]
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 <!A!>
+]]
+
+TEST [[
+---@class A
+---@type A|<!B!>|<!C!>
+]]
+
+TEST [[
+---@class AAA
+---@alias B AAA
+
+---@type B
+]]
+
+TEST [[
+---@alias B <!AAA!>
+]]
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 <!x!> Class
+]]
+
+TEST [[
+---@class Class
+---@param <!y!> Class
+local function f(x)
+    return x
+end
+f()
+]]
+
+TEST [[
+---@class Class
+---@param <!y!> Class
+function F(x)
+    return x
+end
+F()
+]]
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
+<!print!>(stderr) -- `print` is warning but `stderr` is not
+]]
+
+TEST [[
+---@type iolib
+local _ENV = io
+<!print!>(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.<!field3!> + 1)
+print(v:method1())
+print(v.method2())
+print(v:<!method3!>())
+
+---@type Bar
+local v2
+print(v2.field1 + 1)
+print(v2.field2 + 1)
+print(v2.<!field3!> + 1)
+print(v2.field4 + 1)
+print(v2:method1())
+print(v2.method2())
+print(v2:<!method3!>())
+
+local v3 = {}
+print(v3.abc)
+
+---@class Bar2
+local mt3
+function mt3:method() return 1 end
+print(mt3:method())
+]]
+
+-- checkUndefinedField 通过type找到class
+TEST [[
+---@class Foo
+local Foo
+function Foo:method1() end
+
+---@type Foo
+local v
+v:method1()
+v:<!method2!>() -- doc.class.name
+]]
+
+-- checkUndefinedField 通过type找到class，涉及到 class 继承版
+TEST [[
+---@class Foo
+local Foo
+function Foo:method1() end
+---@class Bar: Foo
+local Bar
+function Bar:method3() end
+
+---@type Bar
+local v
+v:method1()
+v:<!method2!>() -- doc.class.name
+v:method3()
+]]
+
+-- checkUndefinedField 类名和类变量同名，类变量被直接使用
+TEST [[
+---@class Foo
+local Foo
+function Foo:method1() end
+Foo:<!method2!>() -- doc.class
+Foo:<!method2!>() -- doc.class
+]]
+
+-- checkUndefinedField 没有@class的不检测
+TEST [[
+local Foo
+function Foo:method1()
+    return Foo:method2() -- table
+end
+]]
+
+-- checkUndefinedField 类名和类变量不同名，类变量被直接使用、使用self
+TEST [[
+---@class Foo
+local mt
+function mt:method1()
+    mt.<!method2!>() -- doc.class
+    self:method1()
+    return self.<!method2!>() -- doc.class.name
+end
+]]
+
+-- checkUndefinedField 当会推导成多个class类型时
+TEST [[
+---@class Foo
+local mt
+function mt:method1() end
+
+---@class Bar
+local mt2
+function mt2:method2() end
+
+---@type Foo
+local v
+---@type Bar
+local v2
+v2 = v
+v2:method1()
+v2:<!method2!>()
+]]
+
+TEST [[
+---@type table
+T1 = {}
+print(T1.f1)
+---@type tablelib
+T2 = {}
+print(T2.<!f2!>)
+]]
+
+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(<!x!>)
+print(<!log!>)
+print(<!X!>)
+print(<!Log!>)
+print(<!y!>)
+print(Z)
+print(_G)
+Z = 1
+]]
+
+TEST [[
+X = table[<!x!>]
+]]
+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 <!x!> 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: <!xxx!>
+]]
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 <!xxx!>: 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
+<!return true!>
+]]
+
+TEST [[
+function X()
+    if X then
+        return false
+    elseif X then
+        return false
+    else
+        return false
+    end
+    <!return true!>
+end
+]]
+
+TEST [[
+while true do
+end
+
+<!print(1)!>
+]]
+
+TEST [[
+while true do
+end
+
+<!print(1)!>
+]]
+
+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 <!function x()
+end!>
+]]
+
+TEST [[
+local x = <!function () end!>
+]]
+
+TEST [[
+local x
+x = <!function () end!>
+]]
+
+TEST [[
+local <!function x()
+end!>
+local <!function y()
+    x()
+end!>
+]]
+
+TEST [[
+local f = <!function () end!>
+]]
+
+TEST [[
+local f;f = <!function () end!>
+]]
+
+TEST [[
+local <!function f() end!>
+]]
+
+TEST [[
+local <!function f()
+    f()
+end!>
+]]
+
+
+TEST [[
+local <!function test()
+end!>
+
+local <!function foo ()
+end!>
+]]
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 [[
+::<!LABEL!>::
+]]
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 <!x!>
+]]
+
+TEST [[
+local y
+local x <close> = y
+]]
+
+TEST [[
+local function x()
+end
+x()
+]]
+
+TEST [[
+return function (x)
+    x.a = 1
+end
+]]
+
+TEST [[
+local <!t!> = {}
+<!t!>.a = 1
+]]
+
+TEST [[
+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 <!mt!> = {}
+function <!mt!>:f()
+end
+]]
+
+TEST [[
+local <!x!> = {}
+<!x!>.a = 1
+]]
+
+TEST [[
+local <!x!> = {}
+<!x!>['a'] = 1
+]]
+
+TEST [[
+local function f(<!self!>)
+    return 'something'
+end
+f()
+]]
+
+TEST [[
+local function f(var)
+    print(var)
+end
+local var
+f(var)
+]]
+
+TEST [[
+local <!t!> = {}
+<!t!>[1] = 1
+]]
+
+config.add(nil, 'Lua.diagnostics.unusedLocalExclude', 'll_*')
+
+TEST [[
+local <!xx!>
+local ll_1
+local ll_2
+local <!ll!>
+]]
+
+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/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/hover/init.lua b/test/hover/init.lua
index 751299bb4..63220a59c 100644
--- a/test/hover/init.lua
+++ b/test/hover/init.lua
@@ -238,14 +238,14 @@ TEST [[
 string.<?sub?>()
 ]]
 [[
-function string.sub(s: string, i: integer, j?: integer)
+function string.sub(s: string|number, i: integer, j?: integer)
   -> string
 ]]
 
 TEST[[
 ('xx'):<?sub?>()
 ]]
-[[function string.sub(s: string, i: integer, j?: integer)
+[[function string.sub(s: string|number, i: integer, j?: integer)
   -> string]]
 
 TEST [[
@@ -272,7 +272,7 @@ TEST [[
 string.<?lower?>()
 ]]
 [[
-function string.lower(s: string)
+function string.lower(s: string|number)
   -> string
 ]]
 
@@ -490,7 +490,7 @@ local self: {
     id: integer = 1,
     remove: function,
     __index: table,
-    __name: string = "obj",
+    __name: string = 'obj',
 }
 ]]
 
@@ -610,7 +610,7 @@ end
 local <?r?> = a(1)
 ]]
 [[
-local r: string = "a"
+local r: string = 'a'
 ]]
 
 TEST[[
@@ -620,7 +620,7 @@ end
 local _, <?r?> = pcall(a, 1)
 ]]
 [[
-local r: string = "a"
+local r: string = 'a'
 ]]
 
 TEST[[
@@ -633,7 +633,7 @@ local n: integer
 TEST[[
 local <?x?> = '\a'
 ]]
-[[local x: string = "\007"]]
+[[local x: string = '\007']]
 
 TEST [[
 local <?t?> = {
@@ -827,9 +827,9 @@ local <?t?> = {
 ]]
 [[
 local t: {
-    [1]: string = "aaa",
-    [2]: string = "bbb",
-    [3]: string = "ccc",
+    [1]: string = 'aaa',
+    [2]: string = 'bbb',
+    [3]: string = 'ccc',
 }
 ]]
 
@@ -1712,13 +1712,13 @@ local <?t?> = {
 ]]
 [[
 local t: {
-    x: string = "e",
-    y: string = "f",
-    ['z']: string = "g",
-    [10]: string = "d",
-    [1]: string = "a",
-    [2]: string = "b",
-    [3]: string = "c"|"h",
+    x: string = 'e',
+    y: string = 'f',
+    ['z']: string = 'g',
+    [10]: string = 'd',
+    [1]: string = 'a',
+    [2]: string = 'b',
+    [3]: string = 'c'|'h',
 }
 ]]
 
@@ -1830,7 +1830,7 @@ local t = {
 local <?x?> = t[#t]
 ]]
 [[
-local x: string = "x"
+local x: string = 'x'
 ]]
 
 TEST [[
@@ -1864,10 +1864,10 @@ local <?x?> = {
 ]]
 [[
 local x: {
-    x: string = "",
-    y: string = "",
-    _x: string = "",
-    _y: string = "",
+    x: string = '',
+    y: string = '',
+    _x: string = '',
+    _y: string = '',
 }
 ]]
 
@@ -2381,6 +2381,25 @@ local obj: B {
 }
 ]]
 
+TEST [[
+---@class A
+local M = {}
+
+---@private
+M.x = 0
+
+---@private
+function M:init()
+    self.x = 1
+end
+
+---@type A
+local <?a?>
+]]
+[[
+local a: A
+]]
+
 TEST [[
 ---@class A
 ---@field x fun(): string
@@ -2430,3 +2449,42 @@ local t: {
     ['y']: integer = 2,
 }
 ]]
+
+TEST [[
+local enum = { a = 1, b = 2 }
+
+local <?t?> = {
+    [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 <?x?>
+]]
+[[
+local x: A
+]]
+
+TEST [[
+---@type A
+local <?f?>
+
+---@enum A
+local t = {
+    x = f,
+}
+]]
+[[
+local f: A
+]]
diff --git a/test/other/filewatch.lua b/test/other/filewatch.lua
index 66a9ccbc7..f5d5a03d9 100644
--- a/test/other/filewatch.lua
+++ b/test/other/filewatch.lua
@@ -9,7 +9,7 @@ fs.create_directories(path)
 
 os.remove((path / 'test.txt'):string())
 
-local _ <close> = fw.watch(path:string())
+local _ <close> = fw.watch(path:string(), true)
 fsu.saveFile(path / 'test.txt', 'test')
 
 local events
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..7c17cb6d3
--- /dev/null
+++ b/test/plugins/ffi/test.lua
@@ -0,0 +1,38 @@
+local lclient = require 'lclient'
+local ws = require 'workspace'
+local furi = require 'file-uri'
+local files = require 'files'
+
+--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,
+    }
+
+    ws.awaitReady(rootUri)
+
+    require 'plugins.ffi.cdef'
+    require 'plugins.ffi.parser'
+    require 'plugins.ffi.builder'
+    TestBuilder()
+end)
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.<!eat!>()
 ]]
+
+TEST [[
+---@class A
+A = {}
+
+function A:<~TestA~>()
+end
+
+---@param param A
+function TestB(param)
+    param:<!TestA!>()
+end
+]]
diff --git a/test/references/common.lua b/test/references/common.lua
index 484418aeb..9cf128a7a 100644
--- a/test/references/common.lua
+++ b/test/references/common.lua
@@ -231,3 +231,18 @@ local t2
 
 t2.<!x!> = 1
 ]]
+
+TEST [[
+---@alias lang 'en' | 'de'
+
+---@class A
+local a
+
+---@type lang
+a.test = 'en'
+
+---@class B
+local b
+
+b.<?<!test!>?> = a.test
+]]
diff --git a/test/rename/init.lua b/test/rename/init.lua
index d4252d1ee..dcb5f9e50 100644
--- a/test/rename/init.lua
+++ b/test/rename/init.lua
@@ -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 17c5cecbe..f46ce0173 100644
--- a/test/signature/init.lua
+++ b/test/signature/init.lua
@@ -235,11 +235,8 @@ end)(<??>)
 {'function (<!a: any!>, 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(<!a: number!>, 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(<?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
@@ -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
@@ -342,3 +330,45 @@ t(<??>)
 {
 'function (<!x: number!>)',
 }
+
+TEST [[
+---@class 😅
+
+---@param a 😅
+---@param b integer
+local function f(a, b)
+end
+
+f(1, 2<??>)
+]]
+{
+'function f(a: 😅, <!b: integer!>)',
+}
+
+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", <!c: string!>)',
+}
+
+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", <!t: integer!>)',
+}
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/modify-luarc.lua b/test/tclient/tests/modify-luarc.lua
index 11de446b0..62d97a417 100644
--- a/test/tclient/tests/modify-luarc.lua
+++ b/test/tclient/tests/modify-luarc.lua
@@ -6,6 +6,7 @@ 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'
 
@@ -34,7 +35,7 @@ lclient():start(function (languageClient)
     })
 
     assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), {
-        ['Lua.runtime.version'] = 'LuaJIT',
+        ['runtime.version'] = 'LuaJIT',
     }))
 
     -------------------------------
@@ -72,7 +73,7 @@ lclient():start(function (languageClient)
     })
 
     assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), {
-        ['Lua.diagnostics.disable'] = {
+        ['diagnostics.disable'] = {
             'undefined-global',
         }
     }))
@@ -140,7 +141,7 @@ lclient():start(function (languageClient)
     })
 
     assert(util.equal(jsonc.decode_jsonc(util.loadFile(configPath)), {
-        ['Lua.runtime.special'] = {
+        ['runtime.special'] = {
             ['include'] = 'require',
         }
     }))
@@ -324,4 +325,53 @@ lclient():start(function (languageClient)
             }
         }
     }))
+
+    -------------------------------
+    -- 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
index ddcdb5d62..e824f23a2 100644
--- a/test/tclient/tests/recursive-runner.lua
+++ b/test/tclient/tests/recursive-runner.lua
@@ -174,8 +174,7 @@ end
         textDocument = { uri = 'file:///test.lua' },
         position = { line = 20, character = 11 },
     })
-    -- TODO
-    --assert(hover1.contents.value:find 'vector3')
+    assert(hover1.contents.value:find 'vector3')
 
     config.set(nil, 'Lua.diagnostics.enable', true)
 end)
diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua
index 33521a0d0..98a07ca3f 100644
--- a/test/type_inference/init.lua
+++ b/test/type_inference/init.lua
@@ -44,6 +44,10 @@ function TEST(wanted)
     end
 end
 
+TEST 'nil' [[
+local <?t?> = nil
+]]
+
 TEST 'string' [[
 local <?var?> = '111'
 ]]
@@ -178,7 +182,7 @@ TEST 'boolean' [[
 <?x?> = a == b
 ]]
 
-TEST 'integer' [[
+TEST 'unknown' [[
 <?x?> = a << b
 ]]
 
@@ -186,7 +190,7 @@ TEST 'integer' [[
 <?x?> = 1 << 2
 ]]
 
-TEST 'string' [[
+TEST 'unknown' [[
 <?x?> = a .. b
 ]]
 
@@ -202,7 +206,7 @@ TEST 'string' [[
 <?x?> = 'a' .. 1.0
 ]]
 
-TEST 'number' [[
+TEST 'unknown' [[
 <?x?> = a + b
 ]]
 
@@ -227,11 +231,11 @@ local a
 <?x?> = - a
 ]]
 
-TEST 'integer' [[
+TEST 'unknown' [[
 <?x?> = 1 + X
 ]]
 
-TEST 'number' [[
+TEST 'unknown' [[
 <?x?> = 1.0 + X
 ]]
 
@@ -1590,7 +1594,7 @@ AAA = {}
 local <?x?> = AAA()
 ]]
 
-TEST 'string|integer' [[
+TEST 'string' [[
 local <?x?>
 x = '1'
 x = 1
@@ -1637,7 +1641,7 @@ function A()
 end
 ]]
 
-TEST 'unknown' [[
+TEST 'string' [[
 local x
 
 function A()
@@ -1758,6 +1762,26 @@ x = '1'
 x = 1
 ]]
 
+TEST 'integer' [[
+local x
+x = true
+do
+    x = 1
+end
+print(<?x?>)
+]]
+
+TEST 'boolean' [[
+local x
+x = true
+function XX()
+    do
+        x = 1
+    end
+end
+print(<?x?>)
+]]
+
 TEST 'integer?' [[
 ---@type integer?
 local <?x?>
@@ -1809,6 +1833,17 @@ end
 print(<?x?>)
 ]]
 
+TEST 'nil' [[
+---@type integer?
+local x
+
+if not x then
+    print(<?x?>)
+end
+
+print(x)
+]]
+
 TEST 'integer' [[
 ---@type integer?
 local x
@@ -1840,6 +1875,15 @@ if xxx and x then
 end
 ]]
 
+TEST 'unknown' [[
+---@type integer?
+local x
+
+if not x and x then
+    print(<?x?>)
+end
+]]
+
 TEST 'integer' [[
 ---@type integer?
 local x
@@ -2277,7 +2321,7 @@ local x
 print(<?x?>)
 ]]
 
-TEST 'unknown?' [[
+TEST 'nil' [[
 ---@type string?
 local x
 
@@ -2351,7 +2395,7 @@ end
 print(<?t?>)
 ]]
 
-TEST 'unknown?' [[
+TEST 'nil' [[
 ---@type integer?
 local t
 
@@ -2376,7 +2420,7 @@ local <?z?> = f()
 
 TEST 'integer|table' [[
 local function returnI()
-    return a + 1
+    return 1
 end
 
 local function f()
@@ -2512,21 +2556,21 @@ end
 print(<?x?>)
 ]]
 
-TEST 'table<unknown, true>' [[
+TEST 'table<xxx, true>' [[
 ---@alias xxx table<xxx, true>
 
 ---@type xxx
 local <?t?>
 ]]
 
-TEST 'unknown[][]' [[
+TEST 'xxx[][]' [[
 ---@alias xxx xxx[]
 
 ---@type xxx
 local <?t?>
 ]]
 
-TEST 'fun(x: fun(x: unknown))' [[
+TEST 'fun(x: fun(x: xxx))' [[
 ---@alias xxx fun(x: xxx)
 
 ---@type xxx
@@ -2580,6 +2624,17 @@ end
 print(<?n?>)
 ]]
 
+TEST 'integer' [[
+---@type integer?
+local n
+
+if not n then
+    os.exit()
+end
+
+print(<?n?>)
+]]
+
 TEST 'table' [[
 ---@type table?
 local n
@@ -2853,10 +2908,7 @@ local <?x?> = echo(b)
 ]]
 
 TEST 'boolean' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -2866,10 +2918,7 @@ local <?x?> = f()
 ]]
 
 TEST 'number' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -2879,10 +2928,7 @@ local <?x?> = f(1)
 ]]
 
 TEST 'boolean' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -2896,10 +2942,7 @@ local <?x?> = f(r0())
 ]]
 
 TEST 'number' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -2913,10 +2956,7 @@ local <?x?> = f(r1())
 ]]
 
 TEST 'boolean' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -2929,10 +2969,7 @@ local <?x?> = f(r0())
 ]]
 
 TEST 'number' [[
----@return boolean
-function f()
-end
-
+---@overload fun():boolean
 ---@param x integer
 ---@return number
 function f(x)
@@ -3160,7 +3197,7 @@ local function f() end
 local x, y, <?z?> = 1, 2, f()
 ]]
 
-TEST 'function' [[
+TEST 'unknown' [[
 local f
 
 print(<?f?>)
@@ -3168,6 +3205,26 @@ print(<?f?>)
 function f() end
 ]]
 
+TEST 'unknown' [[
+local f
+
+do
+    print(<?f?>)
+end
+
+function f() end
+]]
+
+TEST 'function' [[
+local f
+
+function A()
+    print(<?f?>)
+end
+
+function f() end
+]]
+
 TEST 'number' [[
 ---@type number|nil
 local n
@@ -4000,3 +4057,282 @@ local m, v
 
 local <?r?> = m * v
 ]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+    ---@cast t A
+else
+    print(<?t?>)
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+    ---@cast t A
+elseif <?t?> then
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+    ---@cast t A
+    print(t)
+elseif <?t?> then
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+    ---@cast t A
+    print(t)
+elseif <?t?> then
+    ---@cast t A
+    print(t)
+end
+]]
+
+TEST 'function' [[
+local function x()
+    print(<?x?>)
+end
+]]
+
+TEST 'number' [[
+---@type number?
+local x
+
+do
+    if not x then
+        return
+    end
+end
+
+print(<?x?>)
+]]
+
+TEST 'number' [[
+---@type number[]
+local xs
+
+---@type fun(x): number?
+local f
+
+for _, <?x?> in ipairs(xs) do
+    x = f(x)
+end
+]]
+
+TEST 'number' [[
+---@type number?
+X = Y
+
+if X then
+    print(<?X?>)
+end
+]]
+
+TEST 'number' [[
+---@type number|boolean
+X = Y
+
+if type(X) == 'number' then
+    print(<?X?>)
+end
+]]
+
+TEST 'boolean' [[
+---@type number|boolean
+X = Y
+
+if type(X) ~= 'number' then
+    print(<?X?>)
+end
+]]
+
+TEST 'boolean' [[
+---@type number
+X = Y
+
+---@cast X boolean
+
+print(<?X?>)
+]]
+
+TEST 'number' [[
+---@type number
+local t
+
+if xxx == <?t?> then
+    print(t)
+end
+]]
+
+TEST 'V' [[
+---@class V
+X = 1
+
+print(<?X?>)
+]]
+
+TEST 'V' [[
+---@class V
+X.Y = 1
+
+print(X.<?Y?>)
+]]
+
+TEST 'integer' [[
+local x = {}
+
+x.y = 1
+local y = x.y
+x.y = nil
+
+print(<?y?>)
+]]
+
+TEST 'function' [[
+function X()
+    <?Y?>()
+end
+
+function Y()
+end
+]]
+
+TEST 'A_Class' [[
+---@class A_Class
+local A = { x = 5 }
+
+function A:func()
+    for i = 1, <?self?>.x do
+        print(i)
+    end
+
+    self.y = 3
+    self.y = self.y + 3
+end
+]]
+
+TEST 'number' [[
+---@type number?
+local n
+local <?v?> = 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 <?b?> = foo * fooOrBar
+]]
+
+TEST 'number' [[
+local a = 4;
+local b = 2;
+
+local <?c?> = a / b;
+]]
+
+TEST 'string' [[
+local a = '4';
+local b = '2';
+
+local <?c?> = a .. b;
+]]
+
+TEST 'number|{ [1]: string }' [[
+---@alias Some
+---| { [1]: string }
+---| number
+
+local x ---@type Some
+
+print(<?x?>)
+]]
+
+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.<?a?>
+]]
+
+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.<?a?>
+]]
+
+TEST 'integer' [[
+local x = 1
+repeat
+until <?x?>
+]]
+
+-- #2144
+TEST 'A' [=[
+local function f()
+    return {} --[[@as A]]
+end
+
+local <?x?> = f()
+]=]
+
+TEST 'A' [=[
+local function f()
+    ---@type A
+    return {}
+end
+
+local <?x?> = f()
+]=]
+--
diff --git a/theme-tokens.md b/theme-tokens.md
index 1bde2199e..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/lua.tmbundle/blob/master/lua.tmLanguage.json), which is included by default in Visual Studio Code.
+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-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<string, %s>'):format(first.type))
+            return ('object<string, %s>'):format(view(first))
         elseif conf.patternProperties then
             local _, first = next(conf.patternProperties)
             assert(first)
-            md:add('ts', ('Object<string, %s>'):format(first.type))
+            return ('Object<string, %s>'):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 767a66c35..322393bf2 100644
--- a/tools/love-api.lua
+++ b/tools/love-api.lua
@@ -137,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
diff --git a/tools/lovr-api.lua b/tools/lovr-api.lua
index 4c4831fbc..573eea9ed 100644
--- a/tools/lovr-api.lua
+++ b/tools/lovr-api.lua
@@ -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

From db0614da489ebb93d2d0c281235e9b351ed3c7c0 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 14:50:25 +0200
Subject: [PATCH 28/48] Bring in make.lua changes

---
 make.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/make.lua b/make.lua
index 8fba9ac95..296ea9bea 100644
--- a/make.lua
+++ b/make.lua
@@ -1,7 +1,7 @@
 local lm = require 'luamake'
 
-lm.notest = true
-lm.bindir = "bin"
+-- lm.notest = true
+-- lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
 

From e333c52da7febfd1c5092968183376ceb4456b7d Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 15:11:23 +0200
Subject: [PATCH 29/48] Revert build.yml

---
 .github/workflows/build.yml | 160 +++++++++++++++++++++++++-----------
 make.lua                    |   2 -
 2 files changed, 110 insertions(+), 52 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f9d19a3b3..66640e50f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,19 +23,62 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-latest,  target: linux,   platform: linux-x64, zip: x86_64-linux    }
-          - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
-          - { os: macos-11,       target: darwin,  platform: darwin-arm64, zip: arm64-macos  }
-          - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }
+          - { 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:
-      - uses: actions/checkout@v2
+      - 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' && 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: |
+          apk update
+          apk add git ninja bash build-base nodejs linux-headers
+
+      - uses: actions/checkout@v3
         with:
           submodules: recursive
-      - uses: actboy168/setup-luamake@9cfeef6b19646266c655c14c16f7db78446767ab
-      - run: luamake -platform ${{ matrix.platform }}
 
-      - name: Create bin archive
+      - 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
         shell: bash
         run: |
@@ -46,13 +89,24 @@ jobs:
             PKG_VERSION=${GITHUB_SHA:0:7}
           fi
 
+          # Package suffix relative to the platform
+          if [[ "${{ matrix.target }}" = windows ]]; then
+            PKG_SUFFIX="zip"
+          else
+            PKG_SUFFIX="tar.gz"
+          fi
+
           # Package name w/ version
-          PKG_BASENAME="${{ matrix.zip }}"
+          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}.zip"
+          PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}"
 
-          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
+          # Staging area for tarballs
+          PKG_STAGING="ci_staging/$PKG_BASENAME"
 
           echo PKG_VERSION=${PKG_VERSION}             >> $GITHUB_OUTPUT
           echo PKG_BASENAME=${PKG_BASENAME}           >> $GITHUB_OUTPUT
@@ -64,50 +118,56 @@ jobs:
         with:
           name: ${{ steps.vars.outputs.PKG_BASENAME }}
           path: |
-            ${{ steps.vars.outputs.PKG_NAME }}
-  create-extension:
-    needs: compile
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions/download-artifact@v3
-      - name: "Create "
+            ${{ env.BIN_DIR }}
+            main.lua
+            debugger.lua
+            LICENSE
+            changelog.md
+            locale
+            doc
+            meta
+            script
+
+      - name: Package tarballs
+        if: startsWith(github.ref, 'refs/tags/')
         shell: bash
         run: |
+          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
+          NAME=${{ steps.vars.outputs.PKG_NAME }}
+
+          # Making the staging area
+          mkdir -p ${STAGING}
+
+          # Copying binary and runtime files to staging area
+          cp -r main.lua debugger.lua LICENSE changelog.md locale meta script ${{ env.BIN_DIR }} ${STAGING}
+
+          # Creating release assets
+          pushd "${STAGING}/" >/dev/null
+          if [[ "${{ matrix.target }}" = windows ]]; then
+            7z -y a ${NAME} * | tail -2
+          else
+            tar czf ${NAME} *
+          fi
+          popd >/dev/null
+
+      # Packaging submodules for homebrew distribution
+      - name: Package submodules
+        id: submodules
+        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.platform == 'darwin-x64' }}
+        run: |
+          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
+          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}*" -x "3rd/json.lua*" -x "log*" -x "ci_staging*"
+
+          echo PKG_SUBMOD_PATH=${PKG_SUBMOD_PATH} >> $GITHUB_OUTPUT
 
-          7z -y x arm64-macos/arm64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/arm64-macos
-          cd defold/lsp-lua-language-server/plugins
-          7z -y a arm64-macos.zip bin
-          rm -r bin
-          cd -
-
-          7z -y x x86_64-macos/x86_64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-macos
-          cd defold/lsp-lua-language-server/plugins
-          7z -y a x86_64-macos.zip bin
-          rm -r bin 
-          cd -
-
-          7z -y x x86_64-win32/x86_64-win32.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-win32
-          cd defold/lsp-lua-language-server/plugins
-          7z -y a x86_64-win32.zip bin
-          rm -r bin 
-          cd -
-
-          7z -y x x86_64-linux/x86_64-linux.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-linux
-          cd defold/lsp-lua-language-server/plugins
-          7z -y a x86_64-linux.zip bin
-          rm -r bin 
-          cd -
-
-          cd defold
-          7z -y a release.zip *
-      - uses: actions/upload-artifact@v2
-        with:
-          name: release
-          path: defold/release.zip
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
         if: startsWith(github.ref, 'refs/tags/')
         with:
           generate_release_notes: true
-          files: defold/release.zip
+          files: |
+            ${{ steps.vars.outputs.PKG_PATH }}
+            ${{ steps.submodules.outputs.PKG_SUBMOD_PATH }}
diff --git a/make.lua b/make.lua
index 296ea9bea..6f5d11907 100644
--- a/make.lua
+++ b/make.lua
@@ -1,7 +1,5 @@
 local lm = require 'luamake'
 
--- lm.notest = true
--- lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
 

From 85ab6b66d17c3388928cb3dc154213ed51f54709 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 15:13:52 +0200
Subject: [PATCH 30/48] Revert "Revert build.yml"

This reverts commit e333c52da7febfd1c5092968183376ceb4456b7d.
---
 .github/workflows/build.yml | 160 +++++++++++-------------------------
 make.lua                    |   2 +
 2 files changed, 52 insertions(+), 110 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 66640e50f..f9d19a3b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,62 +23,19 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { 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    }
+          - { os: ubuntu-latest,  target: linux,   platform: linux-x64, zip: x86_64-linux    }
+          - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
+          - { os: macos-11,       target: darwin,  platform: darwin-arm64, zip: arm64-macos  }
+          - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }
     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' && 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: |
-          apk update
-          apk add git ninja bash build-base nodejs linux-headers
-
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v2
         with:
           submodules: recursive
+      - uses: actboy168/setup-luamake@9cfeef6b19646266c655c14c16f7db78446767ab
+      - 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
+      - name: Create bin archive
         id: vars
         shell: bash
         run: |
@@ -89,24 +46,13 @@ jobs:
             PKG_VERSION=${GITHUB_SHA:0:7}
           fi
 
-          # Package suffix relative to the platform
-          if [[ "${{ matrix.target }}" = windows ]]; then
-            PKG_SUFFIX="zip"
-          else
-            PKG_SUFFIX="tar.gz"
-          fi
-
           # Package name w/ version
-          PKG_BASENAME="${{ env.PROJECT }}-${PKG_VERSION}-${{ matrix.platform }}"
-          if [[ "${{ matrix.libc }}" = musl ]]; then
-            PKG_BASENAME="${PKG_BASENAME}-${{matrix.libc}}"
-          fi
+          PKG_BASENAME="${{ matrix.zip }}"
 
           # Full name of the tarball asset
-          PKG_NAME="${PKG_BASENAME}.${PKG_SUFFIX}"
+          PKG_NAME="${PKG_BASENAME}.zip"
 
-          # Staging area for tarballs
-          PKG_STAGING="ci_staging/$PKG_BASENAME"
+          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
 
           echo PKG_VERSION=${PKG_VERSION}             >> $GITHUB_OUTPUT
           echo PKG_BASENAME=${PKG_BASENAME}           >> $GITHUB_OUTPUT
@@ -118,56 +64,50 @@ jobs:
         with:
           name: ${{ steps.vars.outputs.PKG_BASENAME }}
           path: |
-            ${{ env.BIN_DIR }}
-            main.lua
-            debugger.lua
-            LICENSE
-            changelog.md
-            locale
-            doc
-            meta
-            script
-
-      - name: Package tarballs
-        if: startsWith(github.ref, 'refs/tags/')
+            ${{ steps.vars.outputs.PKG_NAME }}
+  create-extension:
+    needs: compile
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/download-artifact@v3
+      - name: "Create "
         shell: bash
         run: |
-          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
-          NAME=${{ steps.vars.outputs.PKG_NAME }}
-
-          # Making the staging area
-          mkdir -p ${STAGING}
-
-          # Copying binary and runtime files to staging area
-          cp -r main.lua debugger.lua LICENSE changelog.md locale meta script ${{ env.BIN_DIR }} ${STAGING}
-
-          # Creating release assets
-          pushd "${STAGING}/" >/dev/null
-          if [[ "${{ matrix.target }}" = windows ]]; then
-            7z -y a ${NAME} * | tail -2
-          else
-            tar czf ${NAME} *
-          fi
-          popd >/dev/null
-
-      # Packaging submodules for homebrew distribution
-      - name: Package submodules
-        id: submodules
-        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.platform == 'darwin-x64' }}
-        run: |
-          STAGING=${{ steps.vars.outputs.PKG_STAGING }}
-          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}*" -x "3rd/json.lua*" -x "log*" -x "ci_staging*"
-
-          echo PKG_SUBMOD_PATH=${PKG_SUBMOD_PATH} >> $GITHUB_OUTPUT
 
+          7z -y x arm64-macos/arm64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/arm64-macos
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a arm64-macos.zip bin
+          rm -r bin
+          cd -
+
+          7z -y x x86_64-macos/x86_64-macos.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-macos
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a x86_64-macos.zip bin
+          rm -r bin 
+          cd -
+
+          7z -y x x86_64-win32/x86_64-win32.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-win32
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a x86_64-win32.zip bin
+          rm -r bin 
+          cd -
+
+          7z -y x x86_64-linux/x86_64-linux.zip -odefold/lsp-lua-language-server/plugins/bin/x86_64-linux
+          cd defold/lsp-lua-language-server/plugins
+          7z -y a x86_64-linux.zip bin
+          rm -r bin 
+          cd -
+
+          cd defold
+          7z -y a release.zip *
+      - uses: actions/upload-artifact@v2
+        with:
+          name: release
+          path: defold/release.zip
       - name: Publish release assets
         uses: softprops/action-gh-release@v1
         if: startsWith(github.ref, 'refs/tags/')
         with:
           generate_release_notes: true
-          files: |
-            ${{ steps.vars.outputs.PKG_PATH }}
-            ${{ steps.submodules.outputs.PKG_SUBMOD_PATH }}
+          files: defold/release.zip
diff --git a/make.lua b/make.lua
index 6f5d11907..296ea9bea 100644
--- a/make.lua
+++ b/make.lua
@@ -1,5 +1,7 @@
 local lm = require 'luamake'
 
+-- lm.notest = true
+-- lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
 

From a71989ade42dc5b8d9d16eb378b609e640f8a621 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 15:37:26 +0200
Subject: [PATCH 31/48] ADd include

---
 make/code_format.lua | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/make/code_format.lua b/make/code_format.lua
index f884c9af2..d4b15b13f 100644
--- a/make/code_format.lua
+++ b/make/code_format.lua
@@ -10,7 +10,8 @@ lm:source_set 'code_format' {
         "CodeFormatCore/include",
         "LuaParser/include",
         "../bee.lua/3rd/lua",
-        "3rd/wildcards/include"
+        "3rd/wildcards/include",
+        "include"
     },
     sources = {
         -- codeFormatLib

From de882ba1ce56a0ec83f545089d68cf0e64530dc5 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 15:46:38 +0200
Subject: [PATCH 32/48] update submodules

---
 3rd/EmmyLuaCodeStyle | 2 +-
 3rd/bee.lua          | 2 +-
 3rd/love-api         | 2 +-
 3rd/lovr-api         | 2 +-
 3rd/lpeglabel        | 2 +-
 3rd/luamake          | 2 +-
 make.lua             | 2 ++
 make/code_format.lua | 3 +--
 8 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle
index ec2cd0e2c..13b83a946 160000
--- a/3rd/EmmyLuaCodeStyle
+++ b/3rd/EmmyLuaCodeStyle
@@ -1 +1 @@
-Subproject commit ec2cd0e2cca3b1fa2e08c8bc8d146afd1bdfba19
+Subproject commit 13b83a9465da6a45e5dae37a34e8fdccde48069c
diff --git a/3rd/bee.lua b/3rd/bee.lua
index 992cc5778..6bddb34b4 160000
--- a/3rd/bee.lua
+++ b/3rd/bee.lua
@@ -1 +1 @@
-Subproject commit 992cc577800a470366980d895d4a7cd103a30c39
+Subproject commit 6bddb34b4f076bcb6207c53454e65568c173ca10
diff --git a/3rd/love-api b/3rd/love-api
index f43dfa4cc..04d21fc58 160000
--- a/3rd/love-api
+++ b/3rd/love-api
@@ -1 +1 @@
-Subproject commit f43dfa4ccf5e4918b26602dc421fe7e100aab3a1
+Subproject commit 04d21fc58e5e025373862a4383d55d4dcb6fdb9c
diff --git a/3rd/lovr-api b/3rd/lovr-api
index ec774d0fc..0cc2dc0cd 160000
--- a/3rd/lovr-api
+++ b/3rd/lovr-api
@@ -1 +1 @@
-Subproject commit ec774d0fc127d6aef231a3de8aae3d63031c0585
+Subproject commit 0cc2dc0cd6eefd640f5d27eecc8d211a489b7613
diff --git a/3rd/lpeglabel b/3rd/lpeglabel
index b5d763860..912b0b9e8 160000
--- a/3rd/lpeglabel
+++ b/3rd/lpeglabel
@@ -1 +1 @@
-Subproject commit b5d7638608b4e0b65344c361fd4b8d65a742038f
+Subproject commit 912b0b9e8641074408ffc2259e069b188e0c717b
diff --git a/3rd/luamake b/3rd/luamake
index 3f37edc36..295950da7 160000
--- a/3rd/luamake
+++ b/3rd/luamake
@@ -1 +1 @@
-Subproject commit 3f37edc364ba92dd92d3f4569a6b53bea0f4907e
+Subproject commit 295950da763e2bf75956c56fdc7c8c2877aac4ad
diff --git a/make.lua b/make.lua
index 6f5d11907..8b0e9021c 100644
--- a/make.lua
+++ b/make.lua
@@ -1,5 +1,7 @@
 local lm = require 'luamake'
 
+lm.notest = true
+-- lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
 
diff --git a/make/code_format.lua b/make/code_format.lua
index d4b15b13f..f884c9af2 100644
--- a/make/code_format.lua
+++ b/make/code_format.lua
@@ -10,8 +10,7 @@ lm:source_set 'code_format' {
         "CodeFormatCore/include",
         "LuaParser/include",
         "../bee.lua/3rd/lua",
-        "3rd/wildcards/include",
-        "include"
+        "3rd/wildcards/include"
     },
     sources = {
         -- codeFormatLib

From 27507184b9beeac7f80eac86b5bad8e9ff203be6 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Fri, 1 Sep 2023 17:19:05 +0200
Subject: [PATCH 33/48] re-enable all diagnostics

---
 script/proto/diagnostic.lua | 310 ++++++++++++++++++------------------
 1 file changed, 155 insertions(+), 155 deletions(-)

diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua
index faf870b9d..827f914c4 100644
--- a/script/proto/diagnostic.lua
+++ b/script/proto/diagnostic.lua
@@ -46,177 +46,177 @@ m.register {
     status   = 'Opened',
 }
 
--- m.register {
---     'redundant-value',
---     'unbalanced-assignments',
---     'redundant-parameter',
---     'missing-parameter',
---     'missing-return-value',
---     'redundant-return-value',
---     'missing-return',
---     'missing-fields',
--- } {
---     group    = 'unbalanced',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'redundant-value',
+    'unbalanced-assignments',
+    'redundant-parameter',
+    'missing-parameter',
+    'missing-return-value',
+    'redundant-return-value',
+    'missing-return',
+    'missing-fields',
+} {
+    group    = 'unbalanced',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
--- m.register {
---     'need-check-nil',
---     'undefined-field',
---     'cast-local-type',
---     'assign-type-mismatch',
---     'param-type-mismatch',
---     'cast-type-mismatch',
---     'return-type-mismatch',
---     'inject-field',
--- } {
---     group    = 'type-check',
---     severity = 'Warning',
---     status   = 'Opened',
--- }
+m.register {
+    'need-check-nil',
+    'undefined-field',
+    'cast-local-type',
+    'assign-type-mismatch',
+    'param-type-mismatch',
+    'cast-type-mismatch',
+    'return-type-mismatch',
+    'inject-field',
+} {
+    group    = 'type-check',
+    severity = 'Warning',
+    status   = 'Opened',
+}
 
--- m.register {
---     'duplicate-doc-alias',
---     'undefined-doc-class',
---     'undefined-doc-name',
---     'circle-doc-class',
---     'undefined-doc-param',
---     'duplicate-doc-param',
---     'doc-field-no-class',
---     'duplicate-doc-field',
---     'unknown-diag-code',
---     'unknown-cast-variable',
---     'unknown-operator',
--- } {
---     group    = 'luadoc',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'duplicate-doc-alias',
+    'undefined-doc-class',
+    'undefined-doc-name',
+    'circle-doc-class',
+    'undefined-doc-param',
+    'duplicate-doc-param',
+    'doc-field-no-class',
+    'duplicate-doc-field',
+    'unknown-diag-code',
+    'unknown-cast-variable',
+    'unknown-operator',
+} {
+    group    = 'luadoc',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
--- m.register {
---     'incomplete-signature-doc',
---     'missing-global-doc',
---     'missing-local-export-doc',
--- } {
---     group    = 'luadoc',
---     severity = 'Warning',
---     status   = 'None',
--- }
+m.register {
+    'incomplete-signature-doc',
+    'missing-global-doc',
+    'missing-local-export-doc',
+} {
+    group    = 'luadoc',
+    severity = 'Warning',
+    status   = 'None',
+}
 
--- m.register {
---     'codestyle-check'
--- } {
---     group    = 'codestyle',
---     severity = 'Warning',
---     status   = 'None',
--- }
+m.register {
+    'codestyle-check'
+} {
+    group    = 'codestyle',
+    severity = 'Warning',
+    status   = 'None',
+}
 
--- m.register {
---     'spell-check'
--- } {
---     group    = 'codestyle',
---     severity = 'Information',
---     status   = 'None',
--- }
+m.register {
+    'spell-check'
+} {
+    group    = 'codestyle',
+    severity = 'Information',
+    status   = 'None',
+}
 
--- m.register {
---     'name-style-check'
--- } {
---     group    = 'codestyle',
---     severity = 'Warning',
---     status   = 'None',
--- }
+m.register {
+    'name-style-check'
+} {
+    group    = 'codestyle',
+    severity = 'Warning',
+    status   = 'None',
+}
 
--- m.register {
---     'newline-call',
---     'newfield-call',
---     'ambiguity-1',
---     'count-down-loop',
---     'different-requires',
--- } {
---     group    = 'ambiguity',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'newline-call',
+    'newfield-call',
+    'ambiguity-1',
+    'count-down-loop',
+    'different-requires',
+} {
+    group    = 'ambiguity',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
--- m.register {
---     'await-in-sync',
---     'not-yieldable',
--- } {
---     group    = 'await',
---     severity = 'Warning',
---     status   = 'None',
--- }
+m.register {
+    'await-in-sync',
+    'not-yieldable',
+} {
+    group    = 'await',
+    severity = 'Warning',
+    status   = 'None',
+}
 
--- m.register {
---     'no-unknown',
--- } {
---     group    = 'strong',
---     severity = 'Warning',
---     status   = 'None',
--- }
+m.register {
+    'no-unknown',
+} {
+    group    = 'strong',
+    severity = 'Warning',
+    status   = 'None',
+}
 
--- m.register {
---     'redefined-local',
--- } {
---     group    = 'redefined',
---     severity = 'Hint',
---     status   = 'Opened',
--- }
+m.register {
+    'redefined-local',
+} {
+    group    = 'redefined',
+    severity = 'Hint',
+    status   = 'Opened',
+}
 
--- m.register {
---     'undefined-global',
---     'global-in-nil-env',
--- } {
---     group    = 'global',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'undefined-global',
+    'global-in-nil-env',
+} {
+    group    = 'global',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
--- m.register {
---     'lowercase-global',
---     'undefined-env-child',
--- } {
---     group    = 'global',
---     severity = 'Information',
---     status   = 'Any',
--- }
+m.register {
+    'lowercase-global',
+    'undefined-env-child',
+} {
+    group    = 'global',
+    severity = 'Information',
+    status   = 'Any',
+}
 
--- m.register {
---     'global-element',
--- } {
---     group   = 'conventions',
---     severity = 'Warning',
---     status = 'None'
--- }
+m.register {
+    'global-element',
+} {
+    group   = 'conventions',
+    severity = 'Warning',
+    status = 'None'
+}
 
--- m.register {
---     'duplicate-index',
--- } {
---     group    = 'duplicate',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'duplicate-index',
+} {
+    group    = 'duplicate',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
--- m.register {
---     'duplicate-set-field',
--- } {
---     group    = 'duplicate',
---     severity = 'Warning',
---     status   = 'Opened',
--- }
+m.register {
+    'duplicate-set-field',
+} {
+    group    = 'duplicate',
+    severity = 'Warning',
+    status   = 'Opened',
+}
 
--- m.register {
---     'close-non-object',
---     'deprecated',
---     'discard-returns',
---     'invisible',
--- } {
---     group    = 'strict',
---     severity = 'Warning',
---     status   = 'Any',
--- }
+m.register {
+    'close-non-object',
+    'deprecated',
+    'discard-returns',
+    'invisible',
+} {
+    group    = 'strict',
+    severity = 'Warning',
+    status   = 'Any',
+}
 
 ---@return table<string, DiagnosticSeverity>
 function m.getDefaultSeverity()

From fc0b6da79648013f5cd88ac01fcf44f1e1d78358 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Ritzl?= <bjorn.ritzl@gmail.com>
Date: Tue, 3 Oct 2023 09:26:48 +0200
Subject: [PATCH 34/48] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 066c11531..a1ef76524 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # [Defold](https://defold.com/)'s fork of lua language server
 
-This language server works from the Defold editor and can be used to lint Defold projects.
+This language server works from the Defold editor and can be used to lint Defold projects. Learn more: https://forum.defold.com/t/linting-in-the-code-editor/72465
 
 ## Notable changes
 

From cfa98ad0413b47254ce51be7475bfb272f13ba17 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 13:42:18 +0200
Subject: [PATCH 35/48] Use ubuntu 20.04

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f9d19a3b3..4e4046b95 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,7 +23,7 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-latest,  target: linux,   platform: linux-x64, zip: x86_64-linux    }
+          - { os: ubuntu-20.04,   target: linux,   platform: linux-x64, zip: x86_64-linux    }
           - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
           - { os: macos-11,       target: darwin,  platform: darwin-arm64, zip: arm64-macos  }
           - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }

From ea107810ede46a5a1fdbad055a3b93df09a8659d Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 13:48:42 +0200
Subject: [PATCH 36/48] Revert

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4e4046b95..f9d19a3b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,7 +23,7 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-20.04,   target: linux,   platform: linux-x64, zip: x86_64-linux    }
+          - { os: ubuntu-latest,  target: linux,   platform: linux-x64, zip: x86_64-linux    }
           - { os: macos-11,       target: darwin,  platform: darwin-x64, zip: x86_64-macos   }
           - { os: macos-11,       target: darwin,  platform: darwin-arm64, zip: arm64-macos  }
           - { os: windows-latest, target: windows, platform: win32-x64, zip: x86_64-win32    }

From b7a3f0492b7c89b177a2761da3c717aeb1b890a7 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 14:11:04 +0200
Subject: [PATCH 37/48] Use latest luamake

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4ba7649c0..e793e957f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,7 +32,7 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: recursive
-      - uses: actboy168/setup-luamake@9cfeef6b19646266c655c14c16f7db78446767ab
+      - uses: actboy168/setup-luamake@master
       - run: luamake -platform ${{ matrix.platform }}
 
       - name: Create bin archive

From 81e7e9302620cb7fd81f6bb009a6dee8beab8787 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 14:42:12 +0200
Subject: [PATCH 38/48] update submodules

---
 3rd/EmmyLuaCodeStyle | 2 +-
 3rd/bee.lua          | 2 +-
 3rd/json.lua         | 2 +-
 3rd/love-api         | 2 +-
 3rd/lovr-api         | 2 +-
 3rd/luamake          | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle
index 13b83a946..660a26085 160000
--- a/3rd/EmmyLuaCodeStyle
+++ b/3rd/EmmyLuaCodeStyle
@@ -1 +1 @@
-Subproject commit 13b83a9465da6a45e5dae37a34e8fdccde48069c
+Subproject commit 660a26085ff2c1275392a291063404d54fdd32b9
diff --git a/3rd/bee.lua b/3rd/bee.lua
index 6bddb34b4..f0ab1dd5a 160000
--- a/3rd/bee.lua
+++ b/3rd/bee.lua
@@ -1 +1 @@
-Subproject commit 6bddb34b4f076bcb6207c53454e65568c173ca10
+Subproject commit f0ab1dd5a87852e49f42de6db9b372ad78eb5809
diff --git a/3rd/json.lua b/3rd/json.lua
index bd7b7787b..21c9584d3 160000
--- a/3rd/json.lua
+++ b/3rd/json.lua
@@ -1 +1 @@
-Subproject commit bd7b7787bb8b586e59b5afe5886dd1b76c86eb56
+Subproject commit 21c9584d30fa36c542c98b6b1410393318583712
diff --git a/3rd/love-api b/3rd/love-api
index 04d21fc58..728ba001f 160000
--- a/3rd/love-api
+++ b/3rd/love-api
@@ -1 +1 @@
-Subproject commit 04d21fc58e5e025373862a4383d55d4dcb6fdb9c
+Subproject commit 728ba001f3398fd11b0a3909b919a7caf3e329a4
diff --git a/3rd/lovr-api b/3rd/lovr-api
index 0cc2dc0cd..e89c753e1 160000
--- a/3rd/lovr-api
+++ b/3rd/lovr-api
@@ -1 +1 @@
-Subproject commit 0cc2dc0cd6eefd640f5d27eecc8d211a489b7613
+Subproject commit e89c753e1c2849b7533481fcf058095f8e050b9f
diff --git a/3rd/luamake b/3rd/luamake
index 295950da7..bc906eb2d 160000
--- a/3rd/luamake
+++ b/3rd/luamake
@@ -1 +1 @@
-Subproject commit 295950da763e2bf75956c56fdc7c8c2877aac4ad
+Subproject commit bc906eb2de88a6f169633023a227478da5e9f2a6

From b1a573551ab09fee6a79b7637d1a2b027739bd37 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 14:43:14 +0200
Subject: [PATCH 39/48] Delete test.yml

---
 .github/workflows/test.yml | 18 ------------------
 1 file changed, 18 deletions(-)
 delete mode 100644 .github/workflows/test.yml

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index e8ff3632d..000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-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 }}

From 00b1c4731291a8572ac4e6c01658f28dced9e919 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Thu, 2 May 2024 14:55:15 +0200
Subject: [PATCH 40/48] Update download artifact action

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e793e957f..c7f27adab 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -70,7 +70,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
       - name: "Create "
         shell: bash
         run: |

From 91bf73b8c9a44d3c53bbac6e858d4f0b47ec07c7 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 10:05:56 +0200
Subject: [PATCH 41/48] Disable nitpicks

---
 script/core/diagnostics/luacheck.lua | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index 42c03f5cd..c1b462c65 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -43,6 +43,13 @@ return function (uri, callback)
             options = {
                 stds = {},
                 files = {},
+                ignore = {
+                    "212", -- unused args
+                    "611", -- line contains only whitespace
+                    "612", -- line contains trailing whitespace
+                    "614", -- trailing whitespace in a comment
+                    "631"  -- line is too long
+                },
                 globals = lspconfig.get(workspace.rootUri, 'Lua.diagnostics.globals') or {},
             }
         }

From 799f11ff56f13b93e173e906ecf28f6ac375ecaf Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 10:14:08 +0200
Subject: [PATCH 42/48] Remove unnecessary files

---
 .github/workflows/build.yml | 2 +-
 new_release_tag.sh          | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)
 create mode 100755 new_release_tag.sh

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c7f27adab..01c5960e3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -52,7 +52,7 @@ jobs:
           # Full name of the tarball asset
           PKG_NAME="${PKG_BASENAME}.zip"
 
-          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale doc meta script
+          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE script
 
           echo PKG_VERSION=${PKG_VERSION}             >> $GITHUB_OUTPUT
           echo PKG_BASENAME=${PKG_BASENAME}           >> $GITHUB_OUTPUT
diff --git a/new_release_tag.sh b/new_release_tag.sh
new file mode 100755
index 000000000..7e5a84469
--- /dev/null
+++ b/new_release_tag.sh
@@ -0,0 +1,5 @@
+#/usr/bin/env bash
+set -euox pipefail
+VERSION="v1.$(git rev-list --count HEAD)"
+git tag "$VERSION"
+git push origin "$VERSION"
\ No newline at end of file

From 37c6ec3a96ee798d076c8a4cc7f55bc80f6e2921 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 10:28:38 +0200
Subject: [PATCH 43/48] Bring back necessary locale directory

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 01c5960e3..267e0525e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -52,7 +52,7 @@ jobs:
           # Full name of the tarball asset
           PKG_NAME="${PKG_BASENAME}.zip"
 
-          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE script
+          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale script
 
           echo PKG_VERSION=${PKG_VERSION}             >> $GITHUB_OUTPUT
           echo PKG_BASENAME=${PKG_BASENAME}           >> $GITHUB_OUTPUT

From ea57cd203e9fe49608023c293aa4063cd04366c5 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 10:44:18 +0200
Subject: [PATCH 44/48] Include necessary meta

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 267e0525e..fff4f6e4a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -52,7 +52,7 @@ jobs:
           # Full name of the tarball asset
           PKG_NAME="${PKG_BASENAME}.zip"
 
-          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale script
+          7z -y a ${PKG_NAME} ${{ env.BIN_DIR }} main.lua debugger.lua LICENSE locale meta/template/ meta/spell/ script
 
           echo PKG_VERSION=${PKG_VERSION}             >> $GITHUB_OUTPUT
           echo PKG_BASENAME=${PKG_BASENAME}           >> $GITHUB_OUTPUT

From 3fe46064d89991b27c3696ac247dcbc0c135298e Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 10:56:17 +0200
Subject: [PATCH 45/48] Load luacheckrc on Windows

---
 script/core/diagnostics/luacheck.lua | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index c1b462c65..541899565 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -10,6 +10,7 @@ local workspace = require "workspace"
 local util     = require 'utility'
 local fs       = require "luacheck.fs"
 local inspect    = require 'inspect'
+local platform = require 'bee.platform'
 
 
 local config_stack = nil
@@ -33,8 +34,12 @@ return function (uri, callback)
 
     if not config_stack then
         log.info("loading .luacheckrc")
-        local rootUri = (workspace.rootUri or ""):gsub("file://", "")
+        local rootUri = (workspace.rootUri or ""):gsub(platform.os == "windows" and "file:///" or "file://", "")
         local path = fs.join(rootUri, ".luacheckrc")
+        if platform.os == 'windows' then
+            path = path:gsub('/', "\\")
+        end
+
         local global_path = nil
         local config = lcconfig.load_config(path, global_path) or {options={stds={},files={},globals={}}}
 

From 2e417f3ca9dc6e6ff1ed7708cf9d802e18581944 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Wed, 15 May 2024 13:34:21 +0200
Subject: [PATCH 46/48] Make default luacheck settings overridable

---
 script/core/diagnostics/luacheck.lua | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index 541899565..4e8ff0f00 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -43,24 +43,29 @@ return function (uri, callback)
         local global_path = nil
         local config = lcconfig.load_config(path, global_path) or {options={stds={},files={},globals={}}}
 
-        -- get editor provided globals
-        local editor_config = {
+        local editor_defaults = {
             options = {
-                stds = {},
-                files = {},
+                unused_args = false,
+                max_line_length = false,
                 ignore = {
-                    "212", -- unused args
                     "611", -- line contains only whitespace
                     "612", -- line contains trailing whitespace
                     "614", -- trailing whitespace in a comment
-                    "631"  -- line is too long
                 },
+            }
+        }
+
+        -- get editor provided globals
+        local editor_config = {
+            options = {
+                stds = {},
+                files = {},
                 globals = lspconfig.get(workspace.rootUri, 'Lua.diagnostics.globals') or {},
             }
         }
 
         -- create final config stack combining LuaCheck config, Defold globals and editor globals
-        config_stack = lcconfig.stack_configs({ config, editor_config })
+        config_stack = lcconfig.stack_configs({ editor_defaults, config, editor_config })
     end
 
     local text = files.getText(uri)

From 4c0516715fa8c83e57825ea09baf40f917e305a6 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 27 May 2024 13:55:50 +0200
Subject: [PATCH 47/48] Fix error message

---
 script/parser/compile.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/script/parser/compile.lua b/script/parser/compile.lua
index 8dd772dbe..da556f0ec 100644
--- a/script/parser/compile.lua
+++ b/script/parser/compile.lua
@@ -1275,7 +1275,7 @@ local function parseNumber2(start)
             finish  = getPosition(offset - 1, 'right'),
             version = 'LuaJIT',
             info    = {
-                version = 'Lua 5.4',
+                version = State.version,
             }
         }
     end

From 076fc38808e35ade13cd44564cb9dd1ddee50078 Mon Sep 17 00:00:00 2001
From: Vlad Protsenko <vlad@refold.io>
Date: Mon, 10 Jun 2024 14:34:50 +0200
Subject: [PATCH 48/48] Allow disabling globals check

---
 script/core/diagnostics/luacheck.lua | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/script/core/diagnostics/luacheck.lua b/script/core/diagnostics/luacheck.lua
index 4e8ff0f00..10d88d2da 100644
--- a/script/core/diagnostics/luacheck.lua
+++ b/script/core/diagnostics/luacheck.lua
@@ -52,20 +52,14 @@ return function (uri, callback)
                     "612", -- line contains trailing whitespace
                     "614", -- trailing whitespace in a comment
                 },
-            }
-        }
-
-        -- get editor provided globals
-        local editor_config = {
-            options = {
                 stds = {},
                 files = {},
-                globals = lspconfig.get(workspace.rootUri, 'Lua.diagnostics.globals') or {},
+                globals = lspconfig.get(workspace.rootUri, 'Lua.diagnostics.globals') or {} -- use editor-provided globals
             }
         }
 
         -- create final config stack combining LuaCheck config, Defold globals and editor globals
-        config_stack = lcconfig.stack_configs({ editor_defaults, config, editor_config })
+        config_stack = lcconfig.stack_configs({ editor_defaults, config })
     end
 
     local text = files.getText(uri)
