#[cfg(test)]
mod tests {
use crate::handlers::test_lib::{ProviderVirtualWorkspace, VirtualHoverResult, check};
use googletest::prelude::*;
fn dedent(input: &str) -> String {
let lines: Vec<&str> = input.lines().collect();
let mut min_indent = usize::MAX;
for line in &lines {
if line.trim().is_empty() {
continue;
}
let indent = line.chars().take_while(|c| *c == ' ').count();
min_indent = min_indent.min(indent);
}
if min_indent == usize::MAX {
return String::new();
}
let mut out = String::new();
for (i, line) in lines.iter().enumerate() {
let trimmed = if line.len() >= min_indent {
&line[min_indent..]
} else {
line
};
out.push_str(trimmed);
if i + 1 < lines.len() {
out.push('\n');
}
}
out.trim_start_matches('\n').trim_end().to_string()
}
#[gtest]
fn test_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class <??>A
---@field a number
---@field b string
---@field c boolean
"#,
VirtualHoverResult {
value:
"```lua\n(class) A {\n a: number,\n b: string,\n c: boolean,\n}\n```"
.to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_class_index_signature() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class Foo
---@field [integer] string
---@type Foo
local <??>foo
"#,
VirtualHoverResult {
value: "```lua\nlocal foo: Foo {\n [integer]: string,\n}\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_class_nil_type_key_hidden() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class <??>AAA
---@field [true] 1
---@field [nil] 2
"#,
VirtualHoverResult {
value: "```lua\n(class) AAA {\n [true]: integer = 1,\n}\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_right_to_left() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class Node
---@field x number
---@field right Node?
---@return Node
local function createRBNode()
end
---@type Node
local node
if node.right then
else
node.<??>right = createRBNode()
end
"#,
VirtualHoverResult {
value: "```lua\n(field) right: Node\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class Node1
---@field x number
---@return Node1
local function createRBNode()
end
---@type Node1?
local node
if node then
else
<??>node = createRBNode()
end
"#,
VirtualHoverResult {
value: "```lua\nlocal node: Node1 {\n x: number,\n}\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_nil() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class A
---@field a? number
---@type A
local a
local d = a.<??>a
"#,
VirtualHoverResult {
value: "```lua\n(field) a: number?\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_function_infer_return_val() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
local function <??>f(a, b)
a = 1
end
"#,
VirtualHoverResult {
value: "```lua\nlocal function f(a, b)\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_param_string() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@param n string doc
function foo(<??>n)
end
"#,
VirtualHoverResult {
value: dedent(
r#"
```lua
(parameter) n: string
```
---
doc
"#
)
},
));
Ok(())
}
#[gtest]
fn test_hover_param_func() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@param n fun():boolean doc
function foo(<??>n)
end
"#,
VirtualHoverResult {
value: dedent(
r#"
```lua
(parameter) n: fun() -> boolean
```
---
doc
"#
)
},
));
Ok(())
}
#[gtest]
fn test_hover_generic_param_constraint_and_field_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class Animal
---@field name string 名字
---@field age integer 年龄
---@generic T: Animal
---@param animal T
function checkAnimal(<??>animal)
print(animal.age)
end
"#,
VirtualHoverResult {
value: "```lua\n(parameter) animal: T extends Animal\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class Animal
---@field name string 名字
---@field age integer 年龄
---@generic T: Animal
---@param animal T
function checkAnimal(animal)
print(animal.<??>age)
end
"#,
VirtualHoverResult {
value: "```lua\n(field) age: integer\n```\n\n---\n\n年龄".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class Animal
---@field name string 名字
---@field age integer 年龄
---@generic T: Animal
---@param animal T?
function checkAnimal(<??>animal)
end
"#,
VirtualHoverResult {
value: "```lua\n(parameter) animal: T?\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_special_alias_call_type_syntax() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class KeyofHoverShape
---@field name string
---@type keyof KeyofHoverShape
local <??>key
"#,
VirtualHoverResult {
value: "```lua\nlocal key: \"name\"\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class IndexHoverShape
---@field name string
---@type IndexHoverShape["name"]
local <??>value
"#,
VirtualHoverResult {
value: "```lua\nlocal value: string\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class GenericIndexHoverShape
---@field name string
---@alias GenericIndexHoverPick<K extends keyof T, T> T[K]
---@type GenericIndexHoverPick<"name", GenericIndexHoverShape>
local <??>value
"#,
VirtualHoverResult {
value: "```lua\nlocal value: string\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@class ExtendsHoverShape
---@field name string
---@type ExtendsHoverShape extends table and number or string
local <??>is_table
"#,
VirtualHoverResult {
value: "```lua\nlocal is_table: number\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@alias AB<??>C<K extends keyof T, T> T[K]
"#,
VirtualHoverResult {
value: "```lua\n(alias) ABC<K extends keyof T, T> = T[K]\n```".to_string(),
},
));
check!(
ws.check_hover(
r#"
---@alias Co<??>py<T> { readonly [K in keyof T]?: T[K] }
"#,
VirtualHoverResult {
value: "```lua\n(alias) Copy<T> = { readonly [K in keyof T]?: T[K]; }\n```"
.to_string(),
},
)
);
check!(
ws.check_hover(
r#"
---@class BoxHoverShape
---@field name string
---@class BoxHoverShape<??>Box<K extends keyof BoxHoverShape, T>
---@field value T
"#,
VirtualHoverResult {
value:
"```lua\n(class) BoxHoverShapeBox<K extends keyof BoxHoverShape, T>\n```"
.to_string(),
},
)
);
Ok(())
}
#[gtest]
fn test_hover_narrowed_function_type() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@param n integer|fun():boolean
function _G.foo(n)
local f = n
if type(f) ~= 'function' then
f = function()
return true
end
end
local _ = <??>f
end
"#,
VirtualHoverResult {
value: dedent(
r#"
```lua
(parameter) n: fun() -> boolean
```
"#
),
},
));
Ok(())
}
#[gtest]
fn test_decl_desc() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class Buff.AddData
---@field pulse? number 心跳周期
---@type Buff.AddData
local data
data.pu<??>lse
"#,
VirtualHoverResult {
value: "```lua\n(field) pulse: number?\n```\n\n in class `Buff.AddData`\n\n---\n\n心跳周期".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_issue_535() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@type table<string, number>
local t
---@class T1
local a
function a:init(p)
self._c<??>fg = t[p]
end
"#,
VirtualHoverResult {
value: "```lua\n(field) _cfg: number\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
---@type table<string, number>
local t = {
}
---@class T2
local a = {}
function a:init(p)
self._cfg = t[p]
end
---@param p T2
function fun(p)
local x = p._c<??>fg
end
"#,
VirtualHoverResult {
value: "```lua\n(field) _cfg: number\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_signature_desc() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
-- # A
local function a<??>bc()
end
"#,
VirtualHoverResult {
value: "```lua\nlocal function abc()\n```\n\n---\n\n# A".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_class_desc() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---A1
---@class AB<??>C
---A2
"#,
VirtualHoverResult {
value: "```lua\n(class) ABC\n```\n\n---\n\nA1".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_class_bound_local_decl_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
--- This is the MyModule documentation.
--- It should appear when hovering over MyModule.
--- @class MyModule
local My<??>Module
"#,
VirtualHoverResult {
value: dedent(
r#"
```lua
local MyModule: MyModule
```
---
This is the MyModule documentation.
It should appear when hovering over MyModule.
"#,
),
},
));
Ok(())
}
#[gtest]
fn test_hover_class_bound_member_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
--- @class MyModule
local MyModule = {}
--- This is the SubModule documentation.
--- It should appear when hovering over SubModule.
--- @class MyModule.SubModule
MyModule.Sub<??>Module = {}
"#,
VirtualHoverResult {
value: dedent(
r#"
```lua
(field) SubModule: MyModule.SubModule
```
---
This is the SubModule documentation.
It should appear when hovering over SubModule.
"#,
),
},
));
Ok(())
}
#[gtest]
fn test_attribute_hover_uses_arg_types() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@class custom_attribute: Attribute
---@overload fun(value: string)
---@overload fun(value: integer)
---@[custom_at<??>tribute(1)]
local a
"#,
VirtualHoverResult {
value: "```lua\n(class) custom_attribute(value: integer)\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_alias_desc() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
---@alias Tes<??>Alias
---| 'A' # A1
---| 'B' # A2
"#,
VirtualHoverResult {
value: "```lua\n(alias) TesAlias = (\"A\"|\"B\")\n | \"A\" -- A1\n | \"B\" -- A2\n\n```".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_type_desc() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
local export = {
---@type number? activeSub
vvv = nil
}
export.v<??>vv
"#,
VirtualHoverResult {
value: "```lua\n(field) vvv: number?\n```\n\n---\n\nactiveSub".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_field_key() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class ObserverParams
---@field next fun() # 测试
---@param params fun() | ObserverParams
function test(params)
end
"#,
);
check!(ws.check_hover(
r#"
test({
<??>next = function()
end
})
"#,
VirtualHoverResult {
value: "```lua\n(field) ObserverParams.next()\n```\n\n---\n\n测试".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_field_key_for_generic() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class ObserverParams<T>
---@field next fun() # 测试
---@generic T
---@param params fun() | ObserverParams<T>
function test(params)
end
"#,
);
check!(ws.check_hover(
r#"
test({
<??>next = function()
end
})
"#,
VirtualHoverResult {
value: "```lua\n(field) ObserverParams.next()\n```\n\n---\n\n测试".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_before_dot_returns_object_info() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class Node
---@field field number?
---@field method fun(self: Node)
---@type Node
node = {}
function node.method() end
"#,
);
check!(ws.check_hover(
r#"
node<??>.field = nil
"#,
VirtualHoverResult {
value: "```lua\n(global) node: Node {\n field: number?,\n method: function,\n}\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
node<??>:method()
"#,
VirtualHoverResult {
value: "```lua\n(global) node: Node {\n field: number?,\n method: function,\n}\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
node<??>["key"] = "value"
"#,
VirtualHoverResult {
value: "```lua\n(global) node: Node {\n field: number?,\n method: function,\n}\n```".to_string(),
},
));
check!(ws.check_hover(
r#"
node["key"<??>] = "value"
"#,
VirtualHoverResult {
value: "\"key\"".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_see_tag() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
--- Description
---
--- @see a.b.c
local function te<??>st() end
"#,
VirtualHoverResult {
value: "```lua\nlocal function test()\n```\n\n---\n\nDescription\n\n---\n\n@*see* a.b.c".to_string(),
},
));
check!(ws.check_hover(
r#"
--- Description
---
--- @see a.b.c see description
local function te<??>st() end
"#,
VirtualHoverResult {
value: "```lua\nlocal function test()\n```\n\n---\n\nDescription\n\n---\n\n@*see* a.b.c see description".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_other_tag() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
--- Description
---
--- @xyz content
local function te<??>st() end
"#,
VirtualHoverResult {
value: "```lua\nlocal function test()\n```\n\n---\n\nDescription\n\n---\n\n@*xyz* content".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_class_with_nil() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class A
---@field aAnnotation? string a标签
---@class B
---@field bAnnotation? string b标签
"#,
);
check!(ws.check_hover(
r#"
---@type A|B|nil
local defaultOpt = {
aAnnota<??>tion = "a",
}
"#,
VirtualHoverResult {
value:
"```lua\n(field) aAnnotation: string = \"a\"\n```\n\n---\n\na标签".to_string(),
},
));
Ok(())
}
#[gtest]
fn test_hover_right_expr() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_hover(
r#"
local max = 0
local key
if type(key) ~= "number" then
return false
end
m<??>ax = key
"#,
VirtualHoverResult {
value: "```lua\nlocal max: integer\n```".to_string(),
},
));
Ok(())
}
}