local core = require 'core.hover' local files = require 'files' local catch = require 'catch' local config = require 'config' rawset(_G, 'TEST', true) local accept = { ['local'] = true, ['setlocal'] = true, ['getlocal'] = true, ['setglobal'] = true, ['getglobal'] = true, ['field'] = true, ['method'] = true, ['string'] = true, ['number'] = true, ['integer'] = true, ['doc.type.name'] = true, ['doc.class.name'] = true, ['function'] = true, } ---@diagnostic disable: await-in-sync function TEST(script) return function (expect) local newScript, catched = catch(script, '?') files.setText('', newScript) local hover = core.byUri('', catched['?'][1][1]) assert(hover) expect = expect:gsub('^[\r\n]*(.-)[\r\n]*$', '%1'):gsub('\r\n', '\n') local label = hover:string():gsub('\r\n', '\n'):match('```lua[\r\n]*(.-)[\r\n]*```') assert(expect == label) files.remove('') end end TEST [[ local function (a, b) end ]] "function x(a: any, b: any)" TEST [[ local x(a, b) end ]] "function x(a: any, b: any)" TEST [[ local function x(a, b) end () ]] "function x(a: any, b: any)" TEST [[ local mt = {} mt.__index = mt function mt:init(a, b, c) return end local obj = setmetatable({}, mt) obj:(1, '测试') ]] [[ (method) mt:init(a: any, b: any, c: any) ]] --TEST [[ --local mt = {} --mt.__index = mt --mt.type = 'Class' -- --function mt:init(a, b, c) -- return --end -- --local obj = setmetatable({}, mt) -- --obj:(1, '测试') --]] --[[ --function Class:init(a: any, b: any, c: any) --]] --TEST [[ --local mt = {} --mt.__index = mt --mt.__name = 'Class' -- --function mt:init(a, b, c) -- return --end -- --local obj = setmetatable({}, mt) -- --obj:(1, '测试') --]] --[[ --function Class:init(a: any, b: any, c: any) --]] TEST [[ local mt = {} mt.__index = mt function mt:init(a, b, c) return {} end local obj = setmetatable({}, mt) obj:(1, '测试') ]] [[ (method) mt:init(a: any, b: any, c: any) -> table ]] TEST [[ local mt = {} mt.__index = mt function mt:init(a, b, c) return {} end local obj = setmetatable({}, mt) obj:init(1, '测试') obj.(obj, 1, '测试') ]] [[ (method) mt:init(a: any, b: any, c: any) -> table ]] TEST [[ function obj.xxx() end obj.() ]] "function obj.xxx()" TEST [[ obj.() ]] [[(global) obj.xxx: unknown]] TEST [[ local = 1 ]] "local x: integer = 1" TEST [[ = 1 ]] "(global) x: integer = 1" TEST [[ local t = {} t. = 1 ]] "(field) t.x: integer = 1" TEST [[ t = {} t. = 1 ]] "(global) t.x: integer = 1" TEST [[ t = { = 1 } ]] "(field) x: integer = 1" TEST [[ local = {} ]] "local obj: table" --TEST [[ --local mt = {} --mt.__name = 'class' -- --local = setmetatable({}, mt) --]] --"local obj: class {}" --TEST [[ --local mt = {} --mt.name = 'class' --mt.__index = mt -- --local = setmetatable({}, mt) --]] --[[ --local obj: class { -- __index: table, -- name: string = "class", --} --]] --TEST [[ --local mt = {} --mt.TYPE = 'class' --mt.__index = mt -- --local = setmetatable({}, mt) --]] --[[ --local obj: class { -- TYPE: string = "class", -- __index: table, --} --]] --TEST [[ --local mt = {} --mt.Class = 'class' --mt.__index = mt -- --local = setmetatable({}, mt) --]] --[[ --local obj: class { -- Class: string = "class", -- __index: table, --} --]] -- TODO 支持自定义的函数库 --TEST[[ --local fs = require 'bee.filesystem' --local = fs.current_path() --]] --"local root: bee::filesystem" TEST [[ () ]] [[ function print(...any) ]] TEST [[ string.() ]] [[ function string.sub(s: string, i: integer, j?: integer) -> string ]] TEST[[ ('xx'):() ]] [[function string.sub(s: string, i: integer, j?: integer) -> string]] TEST [[ local = collectgarbage() ]] "local v: any" TEST [[ local type w2l:get_default()[] ]] "local type: unknown" TEST [[ () ]] [=[ function load(chunk: string|function, chunkname?: string, mode?: "b"|"bt"|"t", env?: table) -> function? 2. error_message: string? ]=] TEST [[ string.() ]] [[ function string.lower(s: string) -> string ]] -- 不根据传入值推测参数类型 TEST [[ local function x(a, ...) end (1, 2, 3, 4, 5, 6, 7) ]] [[ function x(a: any, ...any) ]] TEST [[ local function x() return y() end () ]] [[ function x() -> unknown ]] TEST [[ local mt = {} function mt:add(a, b) end local function init() return mt end local t = init() t:() ]] [[ (method) mt:add(a: any, b: any) ]] TEST [[ local mt = {} mt.__index = mt function mt:add(a, b) end local function init() return setmetatable({}, mt) end local t = init() t:() ]] [[ (method) mt:add(a: any, b: any) ]] TEST [[ local = - 1000 ]] [[local t: integer = -1000]] -- TODO 暂不支持 --TEST [[ --for in io.lines() do --end --]] --[[local c: string]] TEST [[ local function f() return ... end local = f() ]] [[local n: unknown]] TEST [[ local = table.unpack(t) ]] [[local n: unknown]] TEST [[ local table.pack(n) ]] [[ local n: unknown ]] TEST [[ local s = ]] [[9 个字节,5 个字符]] TEST [[ local n = ]] [[255]] TEST [[ local = { a = 1, b = 2, c = 3, } ]] [[ local t: { a: integer = 1, b: integer = 2, c: integer = 3, } ]] TEST [[ local = {} t.a = 1 t.a = true ]] [[ local t: { a: boolean|integer = 1|true, } ]] TEST [[ local = { a = 1, [1] = 2, [true] = 3, [5.5] = 4, [{}] = 5, [function () end] = 6, ["b"] = 7, ["012"] = 8, } ]] [[ local t: { a: integer = 1, [1]: integer = 2, [true]: integer = 3, [5.5]: integer = 4, b: integer = 7, ["012"]: integer = 8, } ]] TEST [[ local = {} t[#t+1] = 1 t[#t+1] = 1 local any = collectgarbage() t[any] = any ]] [[ local t: table ]] TEST[[ local x = 1 local y = x print() ]] [[ local y: integer = 1 ]] TEST[[ local mt = {} mt.a = 1 mt.b = 2 mt.c = 3 local = setmetatable({}, {__index = mt}) ]] [[ local obj: { a: integer = 1, b: integer = 2, c: integer = 3, } ]] TEST[[ local mt = {} mt.__index = {} function mt:test(a, b) self:() end ]] [[ (method) mt:test(a: any, b: any) ]] TEST[[ local mt = {} mt.__index = mt mt.__name = 'obj' function mt:remove() end local = setmetatable({ id = 1, }, mt) ]] [[ local self: { id: integer = 1, remove: function, __index: table, __name: string = "obj", } ]] TEST [[ print() ]] [[ (global) utf8: utf8lib { char: function, charpattern: string, codepoint: function, codes: function, len: function, offset: function, } ]] TEST [[ print(io.) ]] [[ (global) io.stderr: file* { close: function, flush: function, lines: function, read: function, seek: function, setvbuf: function, write: function, } ]] TEST [[ print() ]] [[ (global) io: iolib { close: function, flush: function, input: function, lines: function, open: function, output: function, popen: function, read: function, stderr: file*, stdin: file*, stdout: file*, tmpfile: function, type: function, write: function, } ]] TEST [[ local = require 'utf8' ]] [[ local sssss: utf8lib { char: function, charpattern: string, codepoint: function, codes: function, len: function, offset: function, } ]] TEST [[ function F(a) end function F(b) end function F(a) end () ]] [[ function F(a: any) ]] -- 不根据参数推断 --TEST[[ --function a(v) -- print() --end --a(1) --]] --[[ --local v: number = 1 --]] -- --TEST[[ --function a(v) -- print() --end --pcall(a, 1) --]] --[[ --local v: number = 1 --]] --TEST[[ --function a(v) -- print() --end --xpcall(a, log.error, 1) --]] --[[ --local v: number = 1 --]] TEST[[ function a(v) return 'a' end local = a(1) ]] [[ local r: string = "a" ]] TEST[[ function a(v) return 'a' end local _, = pcall(a, 1) ]] [[ local r: string = "a" ]] TEST[[ local = rawlen() ]] [[ local n: integer ]] TEST[[ local = '\a' ]] [[local x: string = "\007"]] TEST [[ local = { b = 1, c = 2, d = 3, a = 4, s = 5, y = 6, z = 7, q = 8, g = 9, p = 10, l = 11, } ]] [[ local t: { b: integer = 1, c: integer = 2, d: integer = 3, a: integer = 4, s: integer = 5, y: integer = 6, z: integer = 7, q: integer = 8, g: integer = 9, p: integer = 10, l: integer = 11, } ]] TEST [[ local function () return nil, nil end ]] [[ function f() -> nil 2. nil ]] TEST [[ local function f() return nil end local = f() ]] [[ local x: nil ]] TEST [[ local function () return 1 return nil end ]] [[ function f() -> integer|nil ]] TEST [[ local = { b = 1, c = 2, d = 3, } local e = t.b ]] [[ local t: { b: integer = 1, c: integer = 2, d: integer = 3, } ]] TEST [[ local = { b = 1, c = 2, d = 3, } g.e = t.b ]] [[ local t: { b: integer = 1, c: integer = 2, d: integer = 3, } ]] TEST [[ local t = { v = { b = 1, c = 2, d = 3, } } print(t.) ]] [[ (field) t.v: { b: integer = 1, c: integer = 2, d: integer = 3, } ]] TEST [[ local = { f = io.open(), } ]] [[ local t: { f?: file*, } ]] TEST [[ io.() ]] [[ function io.popen(prog: string, mode?: "r"|"w") -> file*? 2. errmsg: string? ]] TEST [[ ]] [[ (global) _G: _G { arg: string[], assert: function, collectgarbage: function, coroutine: coroutinelib, debug: debuglib, dofile: function, error: function, getfenv: function, getmetatable: function, io: iolib, ipairs: function, load: function, loadfile: function, loadstring: function, math: mathlib, module: function, newproxy: function, next: function, os: oslib, package: packagelib, pairs: function, pcall: function, print: function, rawequal: function, rawget: function, rawlen: function, rawset: function, require: function, select: function, setfenv: function, setmetatable: function, string: stringlib, table: tablelib, tonumber: function, tostring: function, type: function, unpack: function, utf8: utf8lib, warn: function, xpcall: function, _G: _G, _VERSION: string = "Lua 5.4", } ]] TEST [[ local = { 'aaa', 'bbb', 'ccc', } ]] [[ local t: { [1]: string = "aaa", [2]: string = "bbb", [3]: string = "ccc", } ]] TEST [[ local x x = 1 x = 1.0 print() ]] [[ local x: number = 1 ]] TEST [[ local = 1 ]] [[ local x : integer = 1 ]] TEST [[ local x = 1 print() ]] [[ local x : integer = 1 ]] --TEST [[ --local function (b) -- return (b.c and a(b.c) or b) --end --]] --[[ --function a(b: table) -- -> table --]] TEST [[ local = { a = true } local t2 = { [t.a] = function () end, } ]] [[ local t: { a: boolean = true, } ]] TEST [[ local = { [-1] = -1, [0] = 0, [1] = 1, } ]] [[ local t: { [-1]: integer = -1, [0]: integer = 0, [1]: integer = 1, } ]] TEST[[ ---@class Class local = class() ]] [[ local x: Class ]] TEST[[ ---@class Class = class() ]] [[ (global) x: Class ]] TEST[[ local t = { ---@class Class = class() } ]] [[ (field) x: Class ]] TEST[[ ---@class Class local = class() ]] [[ local x: Class ]] TEST[[ ---@class Class = class() ]] [[ (global) x: Class ]] TEST[[ ---@class A ---@class B ---@class C ---@type A|B|C local = class() ]] [[ local x: A|B|C ]] TEST[[ ---@class Class local = { b = 1 } ]] [[ local x: Class { b: integer = 1, } ]] TEST [[ ---@class Class local mt = {} ---@param t Class function f() end ]] [[ (parameter) t: Class ]] TEST [[ ---@class Class local mt = {} ---@param t Class function f(t) print() end ]] [[ (parameter) t: Class ]] TEST [[ ---@class Class ---@param k Class for in pairs(t) do end ]] [[ local k: Class ]] TEST [[ ---@class Class ---@param v Class for k, in pairs(t) do end ]] [[ local v: Class ]] TEST [[ ---@class A ---@class B ---@class C ---@return A|B ---@return C local function () end ]] [[ function f() -> A|B 2. C ]] TEST [[ ---@generic T ---@param x T ---@return T local function (x) end ]] [[ function f(x: ) -> ]] TEST [[ ---@return number local function f() end local = f() ]] [[ local r: number ]] TEST [[ ---@generic T ---@param x T ---@return T local function f(x) end local = f(1) ]] [[ local r: integer = 1 ]] TEST [[ ---@param x number ---@param y boolean local function (x, y) end ]] [[ function f(x: number, y: boolean) ]] TEST [[ ---@class Class ---@vararg Class local function f(...) local _, = ... end f(1, 2, 3) ]] [[ local x: Class ]] TEST [[ ---@class Class ---@vararg Class local function f(...) local = {...} end ]] [[ local t: Class[] ]] TEST [[ ---@class Class ---@vararg Class local function f(...) local t = {...} local = t[1] end ]] [[ local v: Class ]] TEST [[ ---@class Class ---@vararg Class local function f(...) local = {...} end f(1, 2, 3) ]] [[ local t: Class[] ]] TEST [[ ---@class Class ---@param ... Class local function f(...) local _, = ... end f(1, 2, 3) ]] [[ local x: Class ]] TEST [[ ---@class Class ---@param ... Class local function f(...) local t = {...} local = t[1] end ]] [[ local v: Class ]] TEST [[ ---@class Class ---@param ... Class local function f(...) local = {...} end f(1, 2, 3) ]] [[ local t: Class[] ]] TEST [[ ---@type string[] local ]] [[ local x: string[] ]] TEST [[ ---@type string[]|boolean local ]] [[ local x: boolean|string[] ]] TEST [[ ---@type string[] local t local = t[1] ]] [[ local x: string ]] -- TODO --TEST [[ -----@type string[] --local t --for _, in ipairs(t) do --end --]] --[[ --local x: string --]] --TEST [[ -----@type string[] --local t --for _, in pairs(t) do --end --]] --[[ --local x: string --]] --TEST [[ -----@type string[] --local t --for , v in pairs(t) do --end --]] --[[ --local k: integer --]] TEST [[ ---@type table local ]] [[ local x: table ]] --TEST [[ -----@class ClassA -----@class ClassB -- -----@type table --local t --for _, in pairs(t) do --end --]] --[[ --local x: ClassB --]] --TEST [[ -----@class ClassA -----@class ClassB -- -----@type table --local t --for , v in pairs(t) do --end --]] --[[ --local k: ClassA --]] TEST [[ ---@type fun(x: number, y: number):boolean local ]] [[ local f: fun(x: number, y: number):boolean ]] TEST [[ ---@type fun(x: number, y: number):boolean local f local = f() ]] [[ local r: boolean ]] TEST [[ ---@class void ---@param f fun():void function t() end ]] [[ (parameter) f: fun():void ]] TEST [[ ---@type fun(a:any, b:any) local f local t = {f = f} t:() ]] [[ (field) t.f: fun(a: any, b: any) ]] TEST [[ ---@param names string[] local function f() end ]] [[ (parameter) names: string[] ]] TEST [[ ---@return any function () ---@type integer local a return a end ]] [[ function f() -> any ]] TEST [[ ---@return any function f() ---@type integer local a return a end local = f() ]] [[ local x: any ]] TEST [[ ---@overload fun(y: boolean) ---@param x number ---@param y boolean ---@param z string function f(x, y, z) end print() ]] [[ function f(x: number, y: boolean, z: string) ]] TEST [[ ---@type fun(x?: boolean):boolean? local ]] [[ local f: fun(x?: boolean):boolean? ]] TEST [[ ---@param x? number ---@param y? boolean ---@return table?, string? local function (x, y) end ]] [[ function f(x?: number, y?: boolean) -> table? 2. string? ]] TEST [[ ---@return table first, string? second local function (x, y) end ]] [[ function f(x: any, y: any) -> first: table 2. second: string? ]] TEST [[ ---@class Class ---@field x number ---@field y number ---@field z string local ]] [[ local t: Class { x: number, y: number, z: string, } ]] TEST [[ ---@class A ---@type ]] [[ (class) A ]] TEST [[ ---@type string | "'enum1'" | "'enum2'" local ]] [[ local t: string|'enum1'|'enum2' ]] TEST [[ ---@alias A string | "'enum1'" | "'enum2'" ---@type ]] [[ (alias) A 展开为 string|'enum1'|'enum2' ]] TEST [[ ---@alias A string | "'enum1'" | "'enum2'" ---@type A local ]] [[ local t: string|'enum1'|'enum2' ]] TEST [[ ---@type string ---| "'enum1'" ---| "'enum2'" local ]] [[ local t: string|'enum1'|'enum2' ]] TEST [[ ---@class c t = {} ---@overload fun() function .f() end ]] [[ (global) t: c { f: function, } ]] TEST [[ ---@class c local t = {} ---@overload fun() function t.() end ]] [[ function c.f() ]] TEST [[ ---@class c t = {} ---@overload fun() function t.() end ]] [[ function t.f() ]] TEST [[ ---@class C ---@field field any ---@type C local ]] [[ local c: C { field: any, } ]] TEST [[ ---@class C ---@field field any ---@return C local function f() end local = f() ]] [[ local c: C { field: any, } ]] TEST [[ ---@param callback fun(x: integer, ...) local function f() end ]] [[ (parameter) callback: fun(x: integer, ...any) ]] TEST [[ local --- @type boolean ]] [[ local x: boolean ]] TEST [[ local x --- @type boolean local ]] [[ local y: unknown ]] TEST [[ ---@class Object ---@field a string ---@type Object[] local t local = t[1] print(v.a) ]] [[ local v: Object { a: string, } ]] TEST [[ ---@class Object ---@field a string ---@type Object[] local t local = t[i] print(v.a) ]] [[ local v: Object { a: string, } ]] TEST [[ ---@class C ---@field x string local t ---@generic T ---@param v T ---@param message any ---@return T local function assert(v, message) end local = assert(t) ]] [[ local v: C { x: string, } ]] TEST [[ local --测试 ]] [[ local x: unknown ]] TEST [[ ---@type unknown local t.a = 1 ]] [[ local t: { a: integer = 1, } ]] TEST [[ ---@return number local function f() end local = f() print(u.x) ]] [[ local u: number { x: unknown, } ]] TEST [[ ---@generic K, V ---@param t table ---@return K ---@return V local function next(t) end ---@type table local t local k, v = next() ]] [[ local t: table ]] TEST [[ ---@class A ---@class B: A ---@class C: B ---@class D: C ---@class E: D local m function m:f() return end ]] [[ (self) self: E { f: function, } ]] TEST [[ local f () ]] [[ local f: unknown ]] TEST [[ ---@class Position ---@field x number ---@field y number ---@field z? number ---@field [3]? number local t.z = any ]] [[ local t: Position { x: number, y: number, z?: number, [3]?: number, } ]] TEST [[ ---@type { x: string, y: number, z: boolean } local ]] [[ local t: { x: string, y: number, z: boolean, } ]] TEST [[ .b = 10 * 60 ]] [[ (global) a: { b: integer = 600, } ]] TEST [[ a. = 10 * 60 ]] [[ (global) a.b: integer = 600 ]] TEST [[ a..c = 1 * 1 ]] [[ (global) a.b: { c: integer = 1, } ]] TEST [[ local = { 'a', 'b', 'c', [10] = 'd', x = 'e', y = 'f', ['z'] = 'g', [3] = 'h', } ]] [[ local t: { x: string = "e", y: string = "f", z: string = "g", [1]: string = "a", [2]: string = "b", [3]: string = "c"|"h", [10]: string = "d", } ]] TEST [[ function f1.f2.() end ]] [[ function f1.f2.f3() ]] TEST [[ local t = nil t.() ]] [[ (field) t.x: unknown ]] --TEST [[ -----@class A --local a -- --local b --b = a -- --print(b.) --]] --[[ --(field) A.x: unknown --]] TEST [[ ---@return nil local function () end ]] [[ function f() -> nil ]] TEST [[ ---@async local function () end ]] [[ (async) function f() ]] TEST [[ ---@type function local ]] [[ local f: function ]] TEST [[ ---@type async fun() local ]] [[ local f: async fun() ]] config.set(nil, 'Lua.runtime.nonstandardSymbol', { '//' }) TEST [[ local = 1 // 2 ]] [[ local x: integer = 1 ]] config.set(nil, 'Lua.runtime.nonstandardSymbol', {}) config.set(nil, 'Lua.hover.expandAlias', false) TEST [[ ---@alias uri string ---@type uri local ]] [[ local uri: uri ]] config.set(nil, 'Lua.hover.expandAlias', true) TEST [[ ---@alias uri string ---@type uri local ]] [[ local uri: string ]] TEST [[ local = '1' .. '2' ]] [[ local x: string = "12" ]] TEST [[ local t = { x = 1, [1] = 'x', } local = t[#t] ]] [[ local x: string = "x" ]] TEST [[ local x = { a = 1, b = 2, [1] = 10, } local y = { _ = x.a, _ = x.b, [1] = , } ]] [[ local x: { a: integer = 1, b: integer = 2, [1]: integer = 10, } ]] TEST [[ local = { _x = '', _y = '', x = '', y = '', } ]] [[ local x: { x: string = "", y: string = "", _x: string = "", _y: string = "", } ]] TEST [[ ---@class A ---@field x string ---@class B: A ---@field y string ---@type B local ]] [[ local t: B { x: string, y: string, } ]] TEST [[ ---@class A ---@field x string ---@class B: A ---@field x integer ---@field y string ---@type B local ]] [[ local t: B { x: integer, y: string, } ]] TEST [[ local local function f() x end ]] [[ local x: unknown ]] TEST [[ local x local function f() end ]] [[ (upvalue) x: unknown ]] TEST [[ ---@type `123 ????` | ` x | y ` local ]] [[ local x: ` x | y `|`123 ????` ]] TEST [[ ---@type any local x print(x.) ]] [[ (field) x.y: unknown ]] TEST [[ ---@async x({}, () end) ]] [[ (async) function () ]] TEST [[ ---@overload fun(x: number, y: number):string ---@overload fun(x: number):number ---@return boolean local function f() end local n1 = () local n2 = f(0) local n3 = f(0, 0) ]] [[ function f() -> boolean ]] TEST [[ ---@overload fun(x: number, y: number):string ---@overload fun(x: number):number ---@return boolean local function f() end local n1 = f() local n2 = (0) local n3 = f(0, 0) ]] [[ local f: fun(x: number):number ]] TEST [[ ---@overload fun(x: number, y: number):string ---@overload fun(x: number):number ---@return boolean local function f() end local n1 = f() local n2 = f(0) local n3 = (0, 0) ]] [[ local f: fun(x: number, y: number):string ]] TEST [[ ---@class A local mt ---@type integer mt.x = 1 mt.y = true ---@type A local ]] [[ local t: A { x: integer, y: boolean = true, } ]] TEST [[ ---@param ... boolean ---@return number ... local function (...) end ]] [[ function f(...boolean) -> ...number ]] TEST [[ ---@param ... boolean ---@return ... local function (...) end ]] [[ function f(...boolean) -> ...unknown ]] TEST [[ ---@type fun():x: number local ]] [[ local f: fun():(x: number) ]] TEST [[ ---@type fun(...: boolean):...: number local ]] [[ local f: fun(...boolean):...number ]] TEST [[ ---@type fun():x: number, y: boolean local ]] [[ local f: fun():(x: number, y: boolean) ]] TEST [[ ---@class MyClass local MyClass = { a = 1 } function MyClass:Test() end ]] [[ (self) self: MyClass { Test: function, a: integer = 1, } ]] TEST [[ local y if X then y = true else y = false end local bool = y bool = bool and y if bool then end print() ]] [[ local bool: boolean = true|false ]]