#[cfg(test)]
mod tests {
use glua_code_analysis::{
DocSyntax, Emmyrc, EmmyrcFilenameConvention, FileId, file_path_to_uri,
};
use googletest::prelude::*;
use lsp_types::{
CompletionItemKind, CompletionItemTag, CompletionResponse, CompletionTriggerKind,
Documentation, InsertTextFormat, MarkupContent,
};
use std::{
fs,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
use tokio_util::sync::CancellationToken;
use crate::handlers::completion::{completion, completion_with_deprecated_tag_support};
use crate::handlers::test_lib::{ProviderVirtualWorkspace, VirtualCompletionItem, check};
#[gtest]
fn test_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
local zabcde
za<??>
"#,
vec![VirtualCompletionItem {
label: "zabcde".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
}],
));
Ok(())
}
#[gtest]
fn test_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@overload fun(event: "AAA", callback: fun(trg: string, data: number)): number
---@overload fun(event: "BBB", callback: fun(trg: string, data: string)): string
local function test(event, callback)
end
test("AAA", function(trg, data)
<??>
end)
"#,
vec![
VirtualCompletionItem {
label: "data".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
},
VirtualCompletionItem {
label: "trg".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
},
VirtualCompletionItem {
label: "test".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("(event, callback)".to_string()),
},
],
));
check!(ws.check_completion(
r#"
---@overload fun(event: "AAA", callback: fun(trg: string, data: number)): number
---@overload fun(event: "BBB", callback: fun(trg: string, data: string)): string
local function test(event, callback)
end
test(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"AAA\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"BBB\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "test".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("(event, callback)".to_string()),
},
],
));
check!(ws.check_completion_with_kind(
r#"
---@overload fun(event: "AAA", callback: fun(trg: string, data: number)): number
---@overload fun(event: "BBB", callback: fun(trg: string, data: string)): string
local function test(event, callback)
end
test(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"AAA\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"BBB\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_3() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class Test
---@field event fun(a: "A", b: number)
---@field event fun(a: "B", b: string)
local Test = {}
Test.event(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"A\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"B\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion(
r#"
---@class Test1
---@field event fun(a: "A", b: number)
---@field event fun(a: "B", b: string)
local Test = {}
Test.event(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"A\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"B\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "Test".to_string(),
kind: CompletionItemKind::CLASS,
..Default::default()
},
],
));
check!(ws.check_completion(
r#"
---@class Test2
---@field event fun(a: "A", b: number)
---@field event fun(a: "B", b: string)
local Test = {}
Test.<??>
"#,
vec![VirtualCompletionItem {
label: "event".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("(a, b)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_4() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion(
r#"
local isIn = setmetatable({}, {
---@return string <??>
__index = function(t, k) return k end,
})
"#,
vec![]
));
Ok(())
}
#[gtest]
fn test_completion_undefined_global_isstring_guard_offers_string_methods() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---Returns whether the given value is a string.
---@realm shared
---@realm menu
---@source https://wiki.facepunch.com/gmod/Global.isstring
---@param var any
---@return TypeGuard<string> isString # Whether the value is a string.
function _G.isstring(var) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
if isstring(testVar2) then ---@diagnostic disable-line: undefined-global
testVar2:ch<??>() ---@diagnostic disable-line: undefined-global
end
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
assert!(
items.iter().any(|item| item.label == "char"),
"expected string method completion to include 'char', got: {:?}",
items
.iter()
.map(|item| item.label.clone())
.collect::<Vec<_>>()
);
Ok(())
}
#[gtest]
fn test_guarded_global_table_members_complete_when_child_indexes_first() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (child_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
marauth = marauth or {}
marauth.character = marauth.character or {}
function marauth.character:Create()
end
marauth.util:<??>
"#,
)?;
let file_ids = ws.def_files(vec![
(
"gamemodes/test/gamemode/sh_test1.lua",
child_content.as_str(),
),
(
"gamemodes/test_base/gamemode/sh_test1.lua",
r#"
marauth = marauth or {}
marauth.util = marauth.util or {}
function marauth.util:BaseFunction()
end
"#,
),
]);
let child_file = file_ids[0];
let result = completion(
&ws.analysis,
child_file,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let labels = items
.iter()
.map(|item| item.label.clone())
.collect::<Vec<_>>();
assert!(
labels.iter().any(|label| label == "BaseFunction"),
"expected guarded global table completion to include BaseFunction, got: {labels:?}"
);
Ok(())
}
#[gtest]
fn test_5() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion(
r#"
---@class Test
---@field event fun(a: "A", b: number)
---@field event fun(a: "B", b: string)
local Test = {}
Test.event("<??>")
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "B".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
));
check!(ws.check_completion(
r#"
---@overload fun(event: "AAA", callback: fun(trg: string, data: number)): number
---@overload fun(event: "BBB", callback: fun(trg: string, data: string)): string
local function test(event, callback)
end
test("<??>")
"#,
vec![
VirtualCompletionItem {
label: "AAA".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "BBB".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
));
Ok(())
}
#[gtest]
fn test_enum() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion(
r#"
---@overload fun(event: C6.Param, callback: fun(trg: string, data: number)): number
---@overload fun(event: C6.Param, callback: fun(trg: string, data: string)): string
local function test2(event, callback)
end
---@enum C6.Param
local EP = {
A = "A",
B = "B"
}
test2(<??>)
"#,
vec![
VirtualCompletionItem {
label: "EP.A".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "EP.B".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
));
Ok(())
}
#[gtest]
fn test_enum_string() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion(
r#"
---@overload fun(event: C6.Param, callback: fun(trg: string, data: number)): number
---@overload fun(event: C6.Param, callback: fun(trg: string, data: string)): string
local function test2(event, callback)
end
---@enum C6.Param
local EP = {
A = "A",
B = "B"
}
test2("<??>")
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "B".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
));
Ok(())
}
#[gtest]
fn test_literal_union_completion_includes_literal_kind_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@param event "AAA"|"BBB"
local function test(event)
end
test("<??>")
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "AAA")
.ok_or("missing AAA completion")
.or_fail()?;
verify_that!(
item.label_details
.as_ref()
.and_then(|details| details.description.as_ref()),
some(eq("string literal"))
)?;
Ok(())
}
#[gtest]
fn test_integer_union_completion_includes_literal_kind_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@param mode 1|2
local function set_mode(mode)
end
set_mode(<??>)
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "1")
.ok_or("missing 1 completion")
.or_fail()?;
verify_that!(
item.label_details
.as_ref()
.and_then(|details| details.description.as_ref()),
some(eq("integer literal"))
)?;
Ok(())
}
#[gtest]
fn test_type_comparison() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias std.type
---| "nil"
---| "number"
---| "string"
---@param v any
---@return std.type type
function type(v) end
"#,
);
check!(ws.check_completion(
r#"
local a = 1
if type(a) == "<??>" then
elseif type(a) == "boolean" then
end
"#,
vec![
VirtualCompletionItem {
label: "nil".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "number".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "string".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
));
check!(ws.check_completion_with_kind(
r#"
local a = 1
if type(a) == <??> then
end
"#,
vec![
VirtualCompletionItem {
label: "\"nil\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"number\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"string\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion_with_kind(
r#"
local a = 1
if type(a) ~= "nil" then
elseif type(a) == <??> then
end
"#,
vec![
VirtualCompletionItem {
label: "\"nil\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"number\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"string\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_type_comparison_does_not_reuse_earlier_equality_in_same_statement() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias first.domain
---| "alpha"
---| "beta"
---@alias second.domain
---| "x"
---| "y"
---@param v any
---@return first.domain kind1
function kind1(v) end
---@param v any
---@return second.domain kind2
function kind2(v) end
"#,
);
check!(ws.check_completion_with_kind(
r#"
local a = 1
local b = "x"
if (kind1(a) == "alpha") and (kind2(b) == <??>) then
end
"#,
vec![
VirtualCompletionItem {
label: "\"x\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"y\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_type_comparison_across_newline_preserves_expected_type() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias second.domain
---| "x"
---| "y"
---@param v any
---@return second.domain kind2
function kind2(v) end
"#,
);
check!(ws.check_completion_with_kind(
r#"
local b = "x"
if kind2(b) ==
<??> then
end
"#,
vec![
VirtualCompletionItem {
label: "\"x\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"y\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_issue_272() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class Box
---@class BoxyBox : Box
---@class Truck
---@field box Box
local Truck = {}
---@class TruckyTruck : Truck
---@field box BoxyBox
local TruckyTruck = {}
TruckyTruck.<??>
"#,
vec![VirtualCompletionItem {
label: "box".to_string(),
kind: CompletionItemKind::STRUCT,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_function_self() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class A
local A
function A:test()
s<??>
end
"#,
vec![VirtualCompletionItem {
label: "self".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_class_attr() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class (<??>) A
---@field a string
"#,
vec![
VirtualCompletionItem {
label: "partial".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "exact".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "constructor".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "private".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion_with_kind(
r#"
---@class (partial,<??>) B
---@field a string
"#,
vec![
VirtualCompletionItem {
label: "exact".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "constructor".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "private".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion_with_kind(
r#"
---@enum (<??>) C
"#,
vec![
VirtualCompletionItem {
label: "key".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "partial".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "exact".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "private".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_str_tpl_ref_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion_with_kind(
r#"
---@class A
---@class B
---@class C
---@generic T
---@param name `T`
---@return T
local function new(name)
return name
end
local a = new(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"A\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"B\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"C\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_str_tpl_ref_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@namespace N
---@class C
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@class A
---@class B
---@generic T
---@param name N.`T`
---@return T
local function new(name)
return name
end
local a = new(<??>)
"#,
vec![VirtualCompletionItem {
label: "\"C\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_str_tpl_ref_3() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@class Component
---@class C: Component
---@class D: C
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@class A
---@class B
---@generic T: Component
---@param name `T`
---@return T
local function new(name)
return name
end
local a = new(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"C\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"Component\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"D\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_str_tpl_ref_4() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@class C: string
---@class D: C
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@generic T: string
---@param name `T`
---@return T
local function new(name)
return name
end
local a = new(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"C\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"D\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_table_field_function_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion_with_kind(
r#"
---@class T
---@field func fun(self:string) 注释注释
---@type T
local t = {
<??>
}
"#,
vec![VirtualCompletionItem {
label: "func =".to_string(),
kind: CompletionItemKind::PROPERTY,
..Default::default()
}],
CompletionTriggerKind::INVOKED,
));
Ok(())
}
#[gtest]
fn test_table_field_function_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class T
---@field func fun(self:string) 注释注释
---@type T
local t = {
func = <??>
}
"#,
vec![VirtualCompletionItem {
label: "fun".to_string(),
kind: CompletionItemKind::SNIPPET,
label_detail: Some("(self)".to_string()),
}],
CompletionTriggerKind::INVOKED,
));
Ok(())
}
#[gtest]
fn test_issue_499() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion_with_kind(
r#"
---@class T
---@field func fun(a:string): string
---@type T
local t = {
func = <??>
}
"#,
vec![VirtualCompletionItem {
label: "fun".to_string(),
kind: CompletionItemKind::SNIPPET,
label_detail: Some("(a)".to_string()),
}],
CompletionTriggerKind::INVOKED,
));
Ok(())
}
#[gtest]
fn test_enum_field_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@enum Enum
local Enum = {
a = 1,
}
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@param p Enum
function func(p)
local x1 = p.<??>
end
"#,
vec![],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_issue_502() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@param a { foo: { bar: number } }
function buz(a) end
"#,
);
check!(ws.check_completion_with_kind(
r#"
buz({
foo = {
b<??>
}
})
"#,
vec![VirtualCompletionItem {
label: "bar = ".to_string(),
kind: CompletionItemKind::PROPERTY,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_class_function_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class C1
---@field on_add fun(a: string, b: string)
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@type C1
local c1
c1.on_add = <??>
"#,
vec![VirtualCompletionItem {
label: "function(a, b) end".to_string(),
kind: CompletionItemKind::FUNCTION,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_class_function_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class C1
---@field on_add fun(self: C1, a: string, b: string)
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@type C1
local c1
function c1:<??>()
end
"#,
vec![VirtualCompletionItem {
label: "on_add".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("(a, b)".to_string()),
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_class_function_3() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class (partial) SkillMutator
---@field on_add? fun(self: self, owner: string)
---@class (partial) SkillMutator.A
---@field on_add? fun(self: self, owner: string)
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@class (partial) SkillMutator.A
local a
a.on_add = <??>
"#,
vec![VirtualCompletionItem {
label: "function(self, owner) end".to_string(),
kind: CompletionItemKind::FUNCTION,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_class_function_4() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class (partial) SkillMutator
---@field on_add? fun(self: self, owner: string)
---@class (partial) SkillMutator.A
---@field on_add? fun(self: self, owner: string)
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@class (partial) SkillMutator.A
local a
function a:<??>()
end
"#,
vec![VirtualCompletionItem {
label: "on_add".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("(owner)".to_string()),
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_auto_require() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = ws.get_emmyrc();
emmyrc.completion.auto_require_naming_convention = EmmyrcFilenameConvention::KeepClass;
ws.update_emmyrc(emmyrc);
ws.def_file(
"map.lua",
r#"
---@class Map
local Map = {}
return Map
"#,
);
check!(ws.check_completion(
r#"
ma<??>
"#,
vec![VirtualCompletionItem {
label: "Map".to_string(),
kind: CompletionItemKind::MODULE,
label_detail: Some(" (in map)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_auto_require_table_field() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"aaaa.lua",
r#"
---@export
local export = {}
---@enum MapName
export.MapName = {
A = 1,
B = 2,
}
return export
"#,
);
ws.def_file(
"bbbb.lua",
r#"
local export = {}
---@enum PA
export.PA = {
A = 1,
}
return export
"#,
);
check!(ws.check_completion(
r#"
mapn<??>
"#,
vec![VirtualCompletionItem {
label: "MapName".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: Some(" (in aaaa)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_field_is_alias_function() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias ProxyHandler.Setter fun(raw: any)
---@class ProxyHandler
---@field set? ProxyHandler.Setter
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@class MHandler: ProxyHandler
local MHandler
MHandler.set = <??>
"#,
vec![VirtualCompletionItem {
label: "function(raw) end".to_string(),
kind: CompletionItemKind::FUNCTION,
..Default::default()
}],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_namespace_base() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@namespace Reactive
"#,
);
ws.def(
r#"
---@namespace AlienSignals
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@namespace <??>
"#,
vec![
VirtualCompletionItem {
label: "AlienSignals".to_string(),
kind: CompletionItemKind::MODULE,
..Default::default()
},
VirtualCompletionItem {
label: "Reactive".to_string(),
kind: CompletionItemKind::MODULE,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion_with_kind(
r#"
---@namespace Reactive
---@namespace <??>
"#,
vec![],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
check!(ws.check_completion_with_kind(
r#"
---@namespace Reactive
---@using <??>
"#,
vec![VirtualCompletionItem {
label: "using AlienSignals".to_string(),
kind: CompletionItemKind::MODULE,
..Default::default()
}],
CompletionTriggerKind::INVOKED,
));
Ok(())
}
#[gtest]
fn test_auto_require_field_1() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"AAA.lua",
r#"
local function map()
end
return {
map = map,
}
"#,
);
check!(ws.check_completion(
r#"
map<??>
"#,
vec![],
));
Ok(())
}
#[gtest]
fn test_issue_558() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"AAA.lua",
r#"
---@class ability
---@field t abilityType
---@enum (key) abilityType
local abilityType = {
passive = 1,
}
---@param a ability
function test(a)
end
"#,
);
check!(ws.check_completion(
r#"
test({
t = <??>
})
"#,
vec![VirtualCompletionItem {
label: "\"passive\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
}],
));
Ok(())
}
#[gtest]
fn test_index_key_alias() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(" ---@attribute index_alias(name: string)");
check!(ws.check_completion(
r#"
local export = {
[1] = 1, ---@[index_alias("nameX")]
}
export.<??>
"#,
vec![VirtualCompletionItem {
label: "nameX".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 1".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_issue_572() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@class A
---@field optional_num number?
local a = {}
function a:set()
end
--- @class B : A
local b = {}
function b:set()
self.optional_num = 2
end
b.<??>
"#,
vec![
VirtualCompletionItem {
label: "optional_num".to_string(),
kind: CompletionItemKind::FIELD,
..Default::default()
},
VirtualCompletionItem {
label: "set".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("(self) -> nil".to_string()),
},
],
));
Ok(())
}
#[gtest]
fn test_file_start() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
check!(ws.check_completion(
"table<??>",
vec![VirtualCompletionItem {
label: "table".to_string(),
kind: CompletionItemKind::CLASS,
..Default::default()
}],
));
Ok(())
}
#[gtest]
fn test_field_index_function() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@class A<T>
---@[index_alias("next")]
---@field [1] fun()
A = {}
"#,
);
check!(ws.check_completion(
r#"
A.<??>
"#,
vec![VirtualCompletionItem {
label: "next".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_private_config() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = ws.get_emmyrc();
emmyrc.doc.private_name = vec!["_*".to_string()];
ws.update_emmyrc(emmyrc);
ws.def(
r#"
---@class A
---@field _abc number
---@field _next fun()
A = {}
"#,
);
check!(ws.check_completion(
r#"
---@type A
local a
a.<??>
"#,
vec![],
));
check!(ws.check_completion(
r#"
A.<??>
"#,
vec![
VirtualCompletionItem {
label: "_abc".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "_next".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
],
));
Ok(())
}
#[gtest]
fn test_require_private() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = ws.get_emmyrc();
emmyrc.doc.private_name = vec!["_*".to_string()];
ws.update_emmyrc(emmyrc);
ws.def_file(
"a.lua",
r#"
---@class A
---@field _next fun()
local A = {}
return {
A = A,
}
"#,
);
check!(ws.check_completion(
r#"
local A = require("a").A
A.<??>
"#,
vec![],
));
Ok(())
}
#[gtest]
fn test_doc_completion() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.doc.syntax = DocSyntax::Rst;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"mod_empty.lua",
r#"
"#,
);
ws.def_file(
"mod_with_class.lua",
r#"
--- @class mod_with_class.Cls
--- @class mod_with_class.ns1.ns2.Cls
"#,
);
ws.def_file(
"mod_with_class_and_def.lua",
r#"
local ns = {}
--- @class mod_with_class_and_def.Cls
ns.Cls = {}
function ns.foo() end
return ns
"#,
);
ws.def_file(
"mod_with_sub_mod.lua",
r#"
GLOBAL = 0
return {
x = 1
}
"#,
);
ws.def_file(
"mod_with_sub_mod/sub_mod.lua",
r#"
return {
foo = 1,
bar = function() end,
}
"#,
);
ws.def_file(
"cls.lua",
r#"
--- @class Foo
--- @field x integer
--- @field [1] string
"#,
);
check!(ws.check_completion(
r#"
--- :lua:obj:`<??>`
return {
foo = 0
}
"#,
vec![
VirtualCompletionItem {
label: "mod_with_class_and_def".to_string(),
kind: CompletionItemKind::MODULE,
label_detail: None,
},
VirtualCompletionItem {
label: "mod_with_class".to_string(),
kind: CompletionItemKind::MODULE,
label_detail: None,
},
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "GLOBAL".to_string(),
kind: CompletionItemKind::CONSTANT,
label_detail: Some(" = 0".to_string()),
},
VirtualCompletionItem {
label: "mod_with_class_and_def".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "foo".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 0".to_string()),
},
VirtualCompletionItem {
label: "mod_with_class".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "cls".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "mod_empty".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "mod_with_sub_mod".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
check!(ws.check_completion(r"--- :lua:obj:`mod_empty.<??>`", vec![]));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_class.<??>`",
vec![
VirtualCompletionItem {
label: "Cls".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "ns1".to_string(),
kind: CompletionItemKind::MODULE,
label_detail: None,
},
],
));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_class.ns1.<??>`",
vec![VirtualCompletionItem {
label: "ns2".to_string(),
kind: CompletionItemKind::MODULE,
label_detail: None,
}],
));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_class.ns1.ns2.<??>`",
vec![VirtualCompletionItem {
label: "Cls".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
}],
));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_class_and_def.<??>`",
vec![
VirtualCompletionItem {
label: "Cls".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "foo".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
],
));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_sub_mod.<??>`",
vec![
VirtualCompletionItem {
label: "sub_mod".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 1".to_string()),
},
],
));
check!(ws.check_completion(
r"--- :lua:obj:`mod_with_sub_mod.sub_mod.<??>`",
vec![
VirtualCompletionItem {
label: "bar".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
VirtualCompletionItem {
label: "foo".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 1".to_string()),
},
],
));
check!(ws.check_completion(
r"--- :lua:obj:`Foo.<??>`",
vec![
VirtualCompletionItem {
label: "[1]".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
],
));
Ok(())
}
#[gtest]
fn test_doc_completion_in_members() -> Result<()> {
let make_ws = || {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.doc.syntax = DocSyntax::Rst;
ws.analysis.update_config(emmyrc.into());
ws
};
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {}
--- :lua:obj:`<??>`
Foo.y = 0
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 0".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {}
--- :lua:obj:`<??>`
Foo.y = function() end
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {}
--- :lua:obj:`<??>`
function Foo.y() end
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {}
--- :lua:obj:`<??>`
function Foo:y() end
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("(self)".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {
--- :lua:obj:`<??>`
y = 0
}
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 0".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {
--- :lua:obj:`<??>`
y = function() end
}
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some("()".to_string()),
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- @class Foo
--- @field x integer
local Foo = {}
function Foo:init()
--- :lua:obj:`<??>`
self.y = 0
end
"#,
vec![
VirtualCompletionItem {
label: "Foo".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "x".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: None,
},
VirtualCompletionItem {
label: "y".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 0".to_string()),
},
VirtualCompletionItem {
label: "init".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("(self) -> nil".to_string()),
},
],
));
Ok(())
}
#[gtest]
fn test_doc_completion_myst_empty() -> Result<()> {
let make_ws = || {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.doc.syntax = DocSyntax::Myst;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"a.lua",
r#"
---@class A
"#,
);
ws
};
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- {lua:obj}<??>``...
"#,
vec![],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- {lua:obj}`<??>`...
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- {lua:obj}``<??>...
"#,
vec![],
));
Ok(())
}
#[gtest]
fn test_doc_completion_rst_empty() -> Result<()> {
let make_ws = || {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.doc.syntax = DocSyntax::Rst;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"a.lua",
r#"
---@class A
"#,
);
ws
};
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- :lua:obj:<??>``...
"#,
vec![],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- :lua:obj:`<??>`...
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- :lua:obj:``<??>...
"#,
vec![],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- :lua:obj:`<??>...
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
Ok(())
}
#[gtest]
fn test_doc_completion_rst_default_role_empty() -> Result<()> {
let make_ws = || {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.doc.syntax = DocSyntax::Rst;
emmyrc.doc.rst_default_role = Some("lua:obj".to_string());
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"a.lua",
r#"
---@class A
"#,
);
ws
};
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- <??>``...
"#,
vec![],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- `<??>`...
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- ``<??>...
"#,
vec![],
));
let mut ws = make_ws();
check!(ws.check_completion(
r#"
--- `<??>...
"#,
vec![
VirtualCompletionItem {
label: "A".to_string(),
kind: CompletionItemKind::CLASS,
label_detail: None,
},
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
label_detail: None,
},
],
));
Ok(())
}
#[gtest]
fn test_issue_646() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class Base
---@field a string
"#,
);
check!(ws.check_completion(
r#"
---@generic T: Base
---@param file T
function dirname(file)
file.<??>
end
"#,
vec![VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FIELD,
..Default::default()
},],
));
Ok(())
}
#[gtest]
fn test_see_completion() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class Meep
"#,
);
check!(ws.check_completion(
r#"
--- @see M<??>
"#,
vec![
VirtualCompletionItem {
label: "Meep".to_string(),
kind: CompletionItemKind::CLASS,
..Default::default()
},
VirtualCompletionItem {
label: "virtual_0".to_string(),
kind: CompletionItemKind::FILE,
..Default::default()
},
VirtualCompletionItem {
label: "virtual_1".to_string(),
kind: CompletionItemKind::FILE,
..Default::default()
},
],
));
Ok(())
}
#[gtest]
fn test_generic_extends_completion() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"std.lua",
r#"
---@alias std.type
---| "nil"
---| "number"
"#,
);
ws.def(
r#"
---@generic TP: std.type | table
---@param tp `TP`|TP
function is_type(tp)
end
"#,
);
check!(ws.check_completion_with_kind(
r#"
is_type(<??>)
"#,
vec![
VirtualCompletionItem {
label: "\"nil\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
VirtualCompletionItem {
label: "\"number\"".to_string(),
kind: CompletionItemKind::ENUM_MEMBER,
..Default::default()
},
],
CompletionTriggerKind::TRIGGER_CHARACTER,
));
Ok(())
}
#[gtest]
fn test_generic_partial() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias Partial<T> { [P in keyof T]?: T[P]; }
"#,
);
check!(ws.check_completion(
r#"
---@class AA
---@field a string
---@field b number
---@type Partial<AA>
local a = {}
a.<??>
"#,
vec![
VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
},
VirtualCompletionItem {
label: "b".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
}
],
));
Ok(())
}
#[gtest]
fn test_intersection_completion() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class Matchers<T>
---@class Inverse<T>
---@field negate T
---@class Assertions<T>: Matchers<T> & Inverse<T>
"#,
);
check!(ws.check_completion(
r#"
---@type Assertions<number>
local t
t.<??>
"#,
vec![VirtualCompletionItem {
label: "negate".to_string(),
kind: CompletionItemKind::FIELD,
..Default::default()
},],
));
Ok(())
}
#[gtest]
fn test_super_generic() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@class box<T>: T
---@class AAA
---@field a number
---@type box<AAA>
local a = {}
a.<??>
"#,
vec![VirtualCompletionItem {
label: "a".to_string(),
kind: CompletionItemKind::FIELD,
..Default::default()
},],
));
Ok(())
}
#[gtest]
fn test_keyof_enum() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@enum A
local styles = {
reset = 1
}
---@type table<keyof A, string>
local t
t.<??>
"#,
vec![VirtualCompletionItem {
label: "reset".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
},],
));
Ok(())
}
#[gtest]
fn test_generic_constraint() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@alias std.RawGet<T, K> unknown
---@alias std.ConstTpl<T> unknown
---@generic T, K extends keyof T
---@param object T
---@param key K
---@return std.RawGet<T, K>
function pick(object, key)
end
---@class Person
---@field age integer
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@type Person
local person
pick(person, <??>)
"#,
vec![VirtualCompletionItem {
label: "\"age\"".to_string(),
kind: CompletionItemKind::FIELD,
..Default::default()
},],
CompletionTriggerKind::TRIGGER_CHARACTER
),);
Ok(())
}
#[gtest]
fn test_generic_constraint_inline_object_completion() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@generic T, K extends keyof T
---@param object T
---@param key K
function pick(object, key)
end
"#,
);
check!(ws.check_completion_with_kind(
r#"
pick({ foo = 1, bar = 2 }, <??>)
"#,
vec![
VirtualCompletionItem {
label: "\"bar\"".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 2".to_string()),
},
VirtualCompletionItem {
label: "\"foo\"".to_string(),
kind: CompletionItemKind::FIELD,
label_detail: Some(" = 1".to_string()),
},
],
CompletionTriggerKind::TRIGGER_CHARACTER
));
Ok(())
}
#[gtest]
fn test_function_generic_value_is_nil() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def(
r#"
---@class Expect
---@overload fun<T>(actual: T): Assertion<T>
---@class Assertion<T>
---@field toBe fun(self: self)
---@type table
GTable = {}
"#,
);
check!(ws.check_completion_with_kind(
r#"
---@type Expect
local expect = {}
expect(GTable["a"]):<??>
"#,
vec![VirtualCompletionItem {
label: "toBe".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("()".to_string()),
},],
CompletionTriggerKind::TRIGGER_CHARACTER
));
Ok(())
}
#[gtest]
fn test_module_return_signature() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"test.lua",
r#"
---@export global
local function processError()
return 1
end
return processError
"#,
);
check!(ws.check_completion_with_kind(
r#"
processError<??>
"#,
vec![VirtualCompletionItem {
label: "processError".to_string(),
kind: CompletionItemKind::FUNCTION,
label_detail: Some(" (in test)".to_string()),
}],
CompletionTriggerKind::INVOKED
));
Ok(())
}
#[gtest]
fn test_gmod_net_message_completion_disabled_when_gmod_off() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = false;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
util.AddNetworkString("known_message")
"#,
);
check!(ws.check_completion(
r#"
net.Start("<??>")
"#,
vec![],
));
Ok(())
}
#[gtest]
fn test_gmod_net_message_completion_from_registry() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
util.AddNetworkString("known_message")
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.Start("<??>")
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "known_message")
.ok_or("missing known_message completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
let label_details = item
.label_details
.as_ref()
.ok_or("missing label details")
.or_fail()?;
verify_that!(
label_details.detail.as_ref(),
some(eq("(1 registration, 0 receivers)"))
)?;
verify_that!(
label_details.description.as_ref(),
some(eq("GMod net message"))
)?;
Ok(())
}
#[gtest]
fn test_gmod_net_read_completion_in_receive_callback() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.network.completion.smart_read_suggestions = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"addons/test/lua/autorun/server/send.lua",
r#"
net.Start("MyMsg")
net.WriteEntity(e)
net.WriteString("hello")
net.Broadcast()
"#,
);
let (receive_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.Receive("MyMsg", function()
local x = net.<??>
end)
"#,
)?;
let file_id = ws.def_file(
"addons/test/lua/autorun/client/receive.lua",
receive_content.as_str(),
);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let has_smart_prefix = |item: &lsp_types::CompletionItem| {
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_net_read"))
};
verify_that!(
items
.iter()
.any(|item| item.label == "net.ReadEntity" && has_smart_prefix(item)),
eq(true)
)?;
verify_that!(
items
.iter()
.any(|item| item.label == "net.ReadString" && has_smart_prefix(item)),
eq(true)
)?;
Ok(())
}
#[gtest]
fn test_gmod_net_read_completion_uses_snippet_kind_for_unknown_bits() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.network.completion.smart_read_suggestions = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"addons/test/lua/autorun/server/send.lua",
r#"
local BITS = GetConVar("my_bits"):GetInt()
net.Start("MyMsg")
net.WriteUInt(id, BITS)
net.Broadcast()
"#,
);
let (receive_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.Receive("MyMsg", function()
local id = net.<??>
end)
"#,
)?;
let file_id = ws.def_file(
"addons/test/lua/autorun/client/receive.lua",
receive_content.as_str(),
);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "net.ReadUInt")
.ok_or("missing net.ReadUInt completion")
.or_fail()?;
let text_edit = item
.text_edit
.as_ref()
.ok_or("missing net.ReadUInt text edit")
.or_fail()?;
let lsp_types::CompletionTextEdit::Edit(text_edit) = text_edit else {
return fail!("expected text edit for net.ReadUInt completion");
};
verify_eq!(item.kind, Some(CompletionItemKind::SNIPPET))?;
verify_that!(text_edit.new_text.as_str(), eq("net.ReadUInt(${1:bits})"))?;
verify_that!(
item.insert_text.as_deref(),
eq(Some("net.ReadUInt(${1:bits})"))
)?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_net_read_completion_disabled_when_config_off() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.network.completion.smart_read_suggestions = false;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"addons/test/lua/autorun/server/send.lua",
r#"
net.Start("MyMsg")
net.WriteEntity(e)
net.WriteString("hello")
net.Broadcast()
"#,
);
let (receive_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.Receive("MyMsg", function()
local x = net.<??>
end)
"#,
)?;
let file_id = ws.def_file(
"addons/test/lua/autorun/client/receive.lua",
receive_content.as_str(),
);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
verify_that!(
items.iter().any(|item| {
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_net_read"))
}),
eq(false)
)?;
Ok(())
}
#[gtest]
fn test_gmod_net_read_completion_not_suggested_outside_receive_callback() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
verify_that!(
items.iter().any(|item| {
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_net_read"))
}),
eq(false)
)?;
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_from_registry() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
hook.Add("Think", "id", function() end)
function GM:PlayerSpawn(ply) end
function GM:PlayerSpawnSENT(ply, class_name) end
function SANDBOX:PlayerSpawnSENT(ply, class_name) end
function PLUGIN:OnPluginLoaded(client, character) end
---@hook CustomEvent
function PLUGIN:IgnoredName(x, y) end
"#,
);
check!(ws.check_completion(
r#"
hook.Run("<??>")
"#,
vec![
VirtualCompletionItem {
label: "CustomEvent".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: x, y)".to_string()),
},
VirtualCompletionItem {
label: "OnPluginLoaded".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: client, character)".to_string()),
},
VirtualCompletionItem {
label: "PlayerSpawn".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: ply)".to_string()),
},
VirtualCompletionItem {
label: "PlayerSpawnSENT".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(2 methods; args: ply, class_name)".to_string()),
},
VirtualCompletionItem {
label: "Think".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 hook.Add)".to_string()),
},
],
));
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_label_includes_source_description() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
hook.Add("PlayerSpawn", "id", function(a, b) end)
function GM:PlayerSpawn(ply, transition) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
hook.Run("<??>")
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "PlayerSpawn")
.ok_or("missing PlayerSpawn completion")
.or_fail()?;
let label_details = item
.label_details
.as_ref()
.ok_or("missing label details")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
verify_that!(
label_details.detail.as_ref(),
some(eq("(1 hook.Add, 1 method; args: ply, transition)"))
)?;
verify_that!(label_details.description.as_ref(), some(eq("GMod hook")))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_in_hook_add_string_context() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
function GM:PlayerSpawn(ply, transition) end
"#,
);
check!(ws.check_completion(
r#"
hook.Add("<??>", "id", function() end)
"#,
vec![VirtualCompletionItem {
label: "PlayerSpawn".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: ply, transition)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_prefers_method_callback_params_over_hook_add_callback()
-> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
hook.Add("PlayerSpawn", "id", function(a, b) end)
function GM:PlayerSpawn(ply, transition) end
"#,
);
check!(ws.check_completion(
r#"
hook.Run("<??>")
"#,
vec![VirtualCompletionItem {
label: "PlayerSpawn".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 hook.Add, 1 method; args: ply, transition)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_call_snippet_defaults_to_placeholder_args() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
local function SetOwner(owner) end
SetOw<??>
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "SetOwner")
.ok_or("missing SetOwner completion")
.or_fail()?;
verify_that!(
item.insert_text.as_deref(),
eq(Some("SetOwner(${1:owner})"))
)?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_zero_arg_call_snippet_stays_plain_call() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
local function GetOwner() end
GetOw<??>
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "GetOwner")
.ok_or("missing GetOwner completion")
.or_fail()?;
verify_that!(item.insert_text.as_deref(), eq(Some("GetOwner()")))?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_add_completion_uses_staged_snippet() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
hook = hook or {}
function hook.Add(eventName, identifier, func) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
hook.<??>
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "Add")
.ok_or("missing Add completion")
.or_fail()?;
verify_that!(item.insert_text.as_deref(), eq(Some("Add(\"${1}\")")))?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
verify_that!(
item.command
.as_ref()
.map(|command| command.command.as_str()),
eq(Some("editor.action.triggerSuggest"))
)?;
Ok(())
}
#[gtest]
fn test_gmod_hook_add_string_completion_expands_full_call_snippet() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
function GM:PlayerSpawn(ply, transition) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
hook.Add("<??>")
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "PlayerSpawn")
.ok_or("missing PlayerSpawn completion")
.or_fail()?;
let text_edit = item
.text_edit
.as_ref()
.ok_or("missing staged hook.Add text edit")
.or_fail()?;
let lsp_types::CompletionTextEdit::Edit(text_edit) = text_edit else {
return fail!("expected text edit for staged hook.Add completion");
};
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
verify_that!(
text_edit.new_text.as_str(),
eq("PlayerSpawn\", \"${1:identifier}\", function(ply, transition)\n\t$0\nend)")
)?;
verify_that!(text_edit.range.start.line, eq(1))?;
verify_that!(text_edit.range.start.character, eq(22))?;
verify_that!(item.filter_text.as_deref(), eq(Some("PlayerSpawn")))?;
verify_that!(
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_hook_add_")),
eq(true)
)?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_run_string_completion_expands_emit_snippet() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
function GM:PlayerSpawn(ply, transition) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
hook.Run("<??>")
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "PlayerSpawn")
.ok_or("missing PlayerSpawn completion")
.or_fail()?;
let text_edit = item
.text_edit
.as_ref()
.ok_or("missing staged hook.Run text edit")
.or_fail()?;
let lsp_types::CompletionTextEdit::Edit(text_edit) = text_edit else {
return fail!("expected text edit for staged hook.Run completion");
};
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
verify_that!(
text_edit.new_text.as_str(),
eq("PlayerSpawn\", ${1:ply}, ${2:transition})")
)?;
verify_that!(text_edit.range.start.line, eq(1))?;
verify_that!(text_edit.range.start.character, eq(22))?;
verify_that!(item.filter_text.as_deref(), eq(Some("PlayerSpawn")))?;
verify_that!(
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_hook_emit_")),
eq(true)
)?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_call_string_completion_expands_emit_snippet_with_gamemode() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
function GM:PlayerSpawn(ply, transition) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
hook.Call("<??>")
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "PlayerSpawn")
.ok_or("missing PlayerSpawn completion")
.or_fail()?;
let text_edit = item
.text_edit
.as_ref()
.ok_or("missing staged hook.Call text edit")
.or_fail()?;
let lsp_types::CompletionTextEdit::Edit(text_edit) = text_edit else {
return fail!("expected text edit for staged hook.Call completion");
};
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
verify_that!(
text_edit.new_text.as_str(),
eq("PlayerSpawn\", ${1:GAMEMODE}, ${2:ply}, ${3:transition})")
)?;
verify_that!(item.filter_text.as_deref(), eq(Some("PlayerSpawn")))?;
verify_that!(
item.sort_text
.as_deref()
.is_some_and(|sort_text| sort_text.starts_with("000_gmod_hook_emit_")),
eq(true)
)?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_works_in_empty_string_stage() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
function GM:PlayerSpawn(ply, transition) end
"#,
);
let file_id = ws.def(r#"hook.Add("")"#);
let result = completion(
&ws.analysis,
file_id,
lsp_types::Position {
line: 0,
character: 10,
},
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "PlayerSpawn")
.ok_or("missing PlayerSpawn completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
Ok(())
}
#[gtest]
fn test_gmod_net_receive_completion_uses_staged_snippet() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def(
r#"
net = net or {}
function net.Receive(name, func) end
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.<??>
"#,
)?;
let file_id = ws.def(content.as_str());
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "Receive")
.ok_or("missing Receive completion")
.or_fail()?;
verify_that!(item.insert_text.as_deref(), eq(Some("Receive(\"${1}\")")))?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
verify_that!(
item.command
.as_ref()
.map(|command| command.command.as_str()),
eq(Some("editor.action.triggerSuggest"))
)?;
Ok(())
}
#[gtest]
fn test_gmod_net_receive_string_completion_expands_full_call_snippet() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"addons/test/lua/autorun/server/send.lua",
r#"
util.AddNetworkString("MyMsg")
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
net.Receive("<??>")
"#,
)?;
let file_id = ws.def_file(
"addons/test/lua/autorun/server/receive.lua",
content.as_str(),
);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.iter()
.find(|item| item.label == "MyMsg")
.ok_or("missing MyMsg completion")
.or_fail()?;
let text_edit = item
.text_edit
.as_ref()
.ok_or("missing staged net.Receive text edit")
.or_fail()?;
let lsp_types::CompletionTextEdit::Edit(text_edit) = text_edit else {
return fail!("expected text edit for staged net.Receive completion");
};
verify_eq!(item.kind, Some(CompletionItemKind::EVENT))?;
verify_that!(
text_edit.new_text.as_str(),
eq("MyMsg\", function(len, ply)\n\t$0\nend)")
)?;
verify_that!(text_edit.range.start.line, eq(1))?;
verify_that!(text_edit.range.start.character, eq(25))?;
verify_that!(item.insert_text_format, eq(Some(InsertTextFormat::SNIPPET)))?;
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_filters_realm_in_client_context() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"sv_hook.lua",
r#"
function GM:ServerOnlyHook(ply) end
"#,
);
ws.def_file(
"cl_hook.lua",
r#"
function GM:ClientOnlyHook(ply) end
"#,
);
check!(ws.check_completion(
r#"
if CLIENT then
hook.Run("<??>")
end
"#,
vec![VirtualCompletionItem {
label: "ClientOnlyHook".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: ply)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_hook_completion_prefers_annotation_realm_over_branch_realm() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"sh_hook_annotated.lua",
r#"
if SERVER then
---@realm client
function GM:AnnotatedClientHook(ply) end
function GM:ServerOnlyHook(ply) end
end
"#,
);
check!(ws.check_completion(
r#"
if CLIENT then
hook.Run("<??>")
end
"#,
vec![VirtualCompletionItem {
label: "AnnotatedClientHook".to_string(),
kind: CompletionItemKind::EVENT,
label_detail: Some("(1 method; args: ply)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_member_completion_filters_realm_in_client_context() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
check!(ws.check_completion(
r#"
---@class GM
---@type GM
GM = GM or {}
if SERVER then
function GM:ServerOnlyMethod() end
end
if CLIENT then
function GM:ClientOnlyMethod() end
GM:<??>
end
"#,
vec![VirtualCompletionItem {
label: "ClientOnlyMethod".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("()".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_member_completion_prefers_annotation_realm_over_branch_realm() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
check!(ws.check_completion(
r#"
---@class GM
---@type GM
GM = GM or {}
if SERVER then
---@realm client
function GM:AnnotatedClientMethod() end
function GM:ServerOnlyMethod() end
end
if CLIENT then
GM:<??>
end
"#,
vec![VirtualCompletionItem {
label: "AnnotatedClientMethod".to_string(),
kind: CompletionItemKind::METHOD,
label_detail: Some("()".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_member_completion_in_server_consumer_includes_shared_members_for_class_alias_pattern()
-> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"lua/glide/sh_fuel.lua",
r#"
Glide = Glide or {}
Glide.Fuel = Glide.Fuel or {}
---@class Fuel
local Fuel = Glide.Fuel
function Fuel.GetProfile(id)
return id
end
if SERVER then
function Fuel.ServerOnly(id)
return id
end
end
Glide.Fuel = Fuel
"#,
);
let (consumer_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
local FuelModule = Glide.Fuel
FuelModule.<??>
"#,
)?;
let consumer_file = ws.def_file(
"lua/entities/base_glide_car/init.lua",
consumer_content.as_str(),
);
let completion_result = completion(
&ws.analysis,
consumer_file,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let completion_items = match completion_result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let completion_labels = completion_items
.into_iter()
.map(|item| item.label)
.collect::<Vec<_>>();
verify_that!(&completion_labels, contains(eq("GetProfile")))?;
verify_that!(&completion_labels, contains(eq("ServerOnly")))?;
Ok(())
}
#[gtest]
fn test_gmod_member_completion_in_client_consumer_includes_shared_but_excludes_server_members_for_class_alias_pattern()
-> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"lua/glide/sh_fuel.lua",
r#"
Glide = Glide or {}
Glide.Fuel = Glide.Fuel or {}
---@class Fuel
local Fuel = Glide.Fuel
function Fuel.GetProfile(id)
return id
end
if SERVER then
function Fuel.ServerOnly(id)
return id
end
end
Glide.Fuel = Fuel
"#,
);
let (consumer_content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
if CLIENT then
local FuelModule = Glide.Fuel
FuelModule.<??>
end
"#,
)?;
let consumer_file = ws.def_file(
"lua/entities/base_glide_car/cl_init.lua",
consumer_content.as_str(),
);
let completion_result = completion(
&ws.analysis,
consumer_file,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let completion_items = match completion_result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let completion_labels = completion_items
.into_iter()
.map(|item| item.label)
.collect::<Vec<_>>();
verify_that!(&completion_labels, contains(eq("GetProfile")))?;
verify_that!(&completion_labels, not(contains(eq("ServerOnly"))))?;
Ok(())
}
#[gtest]
fn test_gmod_dynamic_field_completion_global_per_type() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.infer_dynamic_fields = true;
emmyrc.gmod.dynamic_fields_global = true;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"assign.lua",
r#"
---@class DynComp.Entity
---@type DynComp.Entity
local ent
ent.testVar = true
"#,
);
check!(ws.check_completion(
r#"
---@type DynComp.Entity
local ent2
ent2.<??>
"#,
vec![VirtualCompletionItem {
label: "testVar".to_string(),
kind: CompletionItemKind::VARIABLE,
..Default::default()
}],
));
Ok(())
}
#[gtest]
fn test_gmod_dynamic_field_completion_file_scoped_when_disabled() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.infer_dynamic_fields = true;
emmyrc.gmod.dynamic_fields_global = false;
ws.analysis.update_config(emmyrc.into());
ws.def_file(
"assign.lua",
r#"
---@class DynCompScoped.Entity
---@type DynCompScoped.Entity
local ent
ent.testVar = true
"#,
);
check!(ws.check_completion(
r#"
---@type DynCompScoped.Entity
local ent2
ent2.<??>
"#,
vec![],
));
Ok(())
}
#[gtest]
fn test_gmod_dynamic_field_completion_for_metatable_instance() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
emmyrc.gmod.infer_dynamic_fields = true;
ws.analysis.update_config(emmyrc.into());
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
local LOCATION = {}
LOCATION.__index = LOCATION
function LOCATION:Init()
local instance = {}
setmetatable(instance, self)
instance._OriginalName = true
return instance
end
function LOCATION:GetOriginalName()
return self.<??>
end
"#,
)?;
let file_id = ws.def(&content);
let completion_result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let completion_items = match completion_result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let completion_labels = completion_items
.into_iter()
.map(|item| item.label)
.collect::<Vec<_>>();
verify_that!(&completion_labels, contains(eq("_OriginalName")))?;
Ok(())
}
#[gtest]
fn test_gmod_plugin_function_decl_completion_uses_gm_fallback_with_param_detail() -> Result<()>
{
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@class GM
---@type GM
GM = GM or {}
function GM:ZzzPluginHook(player, entity) end
local PLUGIN = {}
function PLUGIN:<??>()
end
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::TRIGGER_CHARACTER,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|it| it.label == "ZzzPluginHook")
.ok_or("missing ZzzPluginHook completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::METHOD))?;
let item_detail = item
.label_details
.as_ref()
.and_then(|details| details.detail.clone());
verify_eq!(item_detail, Some("(player, entity)".to_string()))?;
let item_description = item
.label_details
.as_ref()
.and_then(|details| details.description.clone());
verify_eq!(item_description, Some("from GM".to_string()))?;
Ok(())
}
#[gtest]
fn test_gmod_color_global_completion_includes_preview_metadata() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@class Color
---@return Color
function Color(r, g, b, a) end
color_white = Color(255, 255, 255, 255)
color_black = Color(0, 0, 0, 255)
color_w<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "color_white")
.ok_or("missing color_white completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::COLOR))?;
let label_details = item
.label_details
.as_ref()
.ok_or("missing label details")
.or_fail()?;
verify_that!(
label_details.detail.as_ref(),
some(eq(" Color(255, 255, 255, 255)"))
)?;
verify_that!(label_details.description.as_ref(), some(eq("Color")))?;
verify_that!(
item.data
.as_ref()
.and_then(|data| data.get("color"))
.and_then(|color| color.get("hex"))
.and_then(|hex| hex.as_str()),
some(eq("#FFFFFFFF"))
)?;
let resolved = crate::handlers::completion::completion_resolve(
&ws.analysis,
item,
crate::context::ClientId::VSCode,
);
let documentation = resolved
.documentation
.as_ref()
.ok_or("missing resolved documentation")
.or_fail()?;
let Documentation::MarkupContent(MarkupContent { value, .. }) = documentation else {
return fail!("expected markdown documentation");
};
verify_that!(value, not(contains_substring("data:image/svg+xml;utf8,")))?;
verify_that!(value, not(contains_substring("gluals-color-preview")))?;
verify_that!(value, not(contains_substring("rgba(255, 255, 255, 255)")))?;
verify_that!(value, not(contains_substring("---")))?;
verify_that!(value, contains_substring("Color(255, 255, 255, 255)"))?;
Ok(())
}
#[gtest]
fn test_color_completion_preview_metadata_suppressed_when_document_color_disabled() -> Result<()>
{
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = ws.get_emmyrc();
emmyrc.document_color.enable = false;
ws.update_emmyrc(emmyrc);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@class Color
---@return Color
function Color(r, g, b, a) end
color_white = Color(255, 255, 255, 255)
color_w<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "color_white")
.ok_or("missing color_white completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::COLOR))?;
verify_that!(
item.data.as_ref().and_then(|data| data.get("color")),
none()
)?;
Ok(())
}
#[gtest]
fn test_deprecated_completion_uses_completion_item_tag() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@deprecated use new_api
function old_api() end
old<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "old_api")
.ok_or("missing old_api completion")
.or_fail()?;
verify_eq!(item.deprecated, Some(true))?;
verify_eq!(item.tags, Some(vec![CompletionItemTag::DEPRECATED]))?;
let result = completion_with_deprecated_tag_support(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
false,
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "old_api")
.ok_or("missing old_api completion")
.or_fail()?;
verify_eq!(item.deprecated, Some(true))?;
verify_eq!(item.tags, None)?;
Ok(())
}
#[gtest]
fn test_member_completion_uses_method_and_property_kinds() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@class Panel
local PANEL = {}
PANEL.Title = "Scoreboard"
function PANEL:Paint(w, h) end
PANEL.<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let paint = items
.iter()
.find(|item| item.label == "Paint")
.ok_or("missing Paint completion")
.or_fail()?;
verify_eq!(paint.kind, Some(CompletionItemKind::METHOD))?;
verify_that!(
paint
.label_details
.as_ref()
.and_then(|details| details.detail.as_ref()),
some(eq("(self, w, h)"))
)?;
let title = items
.iter()
.find(|item| item.label == "Title")
.ok_or("missing Title completion")
.or_fail()?;
verify_eq!(title.kind, Some(CompletionItemKind::FIELD))?;
Ok(())
}
#[gtest]
fn test_callable_union_member_completion_keeps_callable_kinds() -> Result<()> {
fn completion_kind(source: &str, label: &str) -> Result<Option<CompletionItemKind>> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(source)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
Ok(items
.into_iter()
.find(|item| item.label == label)
.and_then(|item| item.kind))
}
verify_eq!(
completion_kind(
r#"
---@class Panel
---@field Callback fun(self: Panel)|fun(self: Panel, value: number)
---@type Panel
local panel
panel.<??>
"#,
"Callback"
)?,
Some(CompletionItemKind::FUNCTION)
)?;
verify_eq!(
completion_kind(
r#"
---@class Panel
---@field Callback fun(self: Panel)|fun(self: Panel, value: number)
---@type Panel
local panel
panel:<??>
"#,
"Callback"
)?,
Some(CompletionItemKind::METHOD)
)?;
verify_eq!(
completion_kind(
r#"
---@class Panel
---@field Callback { value: number } & fun(self: Panel)
---@type Panel
local panel
panel:<??>
"#,
"Callback"
)?,
Some(CompletionItemKind::METHOD)
)?;
Ok(())
}
#[gtest]
fn test_completion_kinds_use_richer_type_mapping() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
local scoreboard = {}
scoreb<??>
"#,
vec![VirtualCompletionItem {
label: "scoreboard".to_string(),
kind: CompletionItemKind::STRUCT,
..Default::default()
}],
));
check!(ws.check_completion(
r#"
---@type number
local player_count
player_c<??>
"#,
vec![VirtualCompletionItem {
label: "player_count".to_string(),
kind: CompletionItemKind::VALUE,
..Default::default()
}],
));
check!(ws.check_completion(
r#"
---@type never
local no_value
no_v<??>
"#,
vec![VirtualCompletionItem {
label: "no_value".to_string(),
kind: CompletionItemKind::UNIT,
..Default::default()
}],
));
Ok(())
}
#[gtest]
fn test_global_table_namespace_completion_kinds() -> Result<()> {
fn completion_kind(source: &str, label: &str) -> Result<Option<CompletionItemKind>> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(source)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
Ok(items
.into_iter()
.find(|item| item.label == label)
.and_then(|item| item.kind))
}
verify_eq!(
completion_kind(
r#"
ix = ix or {}
ix.character = ix.character or {}
function ix.character.get(id) end
i<??>
"#,
"ix"
)?,
Some(CompletionItemKind::CLASS)
)?;
verify_eq!(
completion_kind(
r#"
ix = ix or {}
ix.character = ix.character or {}
function ix.character.get(id) end
ix.<??>
"#,
"character"
)?,
Some(CompletionItemKind::INTERFACE)
)?;
verify_eq!(
completion_kind(
r#"
ix = ix or {}
ix.character = ix.character or {}
function ix.character.get(id) end
ix.character.<??>
"#,
"get"
)?,
Some(CompletionItemKind::FUNCTION)
)?;
Ok(())
}
#[gtest]
fn test_scalar_constant_completion_includes_literal_detail() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
local MAX_PLAYERS = 128
MAX_PLAY<??>
"#,
vec![VirtualCompletionItem {
label: "MAX_PLAYERS".to_string(),
kind: CompletionItemKind::CONSTANT,
label_detail: Some(" = 128".to_string()),
}],
));
check!(ws.check_completion(
r#"
local GAMEMODE_NAME = "sandbox"
GAMEMODE<??>
"#,
vec![VirtualCompletionItem {
label: "GAMEMODE_NAME".to_string(),
kind: CompletionItemKind::CONSTANT,
label_detail: Some(" = \"sandbox\"".to_string()),
}],
));
check!(ws.check_completion(
r#"
local IS_CLIENTSIDE = true
IS_CLIENT<??>
"#,
vec![VirtualCompletionItem {
label: "IS_CLIENTSIDE".to_string(),
kind: CompletionItemKind::CONSTANT,
label_detail: Some(" = true".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_doc_boolean_constant_completion_is_constant() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@type true
local IS_CLIENTSIDE
IS_CLIENT<??>
"#,
vec![VirtualCompletionItem {
label: "IS_CLIENTSIDE".to_string(),
kind: CompletionItemKind::CONSTANT,
label_detail: Some(" = true".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_hex_color_string_completion_includes_preview_metadata() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r##"
local ACCENT_COLOR = "#112233FF"
ACCENT<??>
"##,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "ACCENT_COLOR")
.ok_or("missing ACCENT_COLOR completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::COLOR))?;
let label_details = item
.label_details
.as_ref()
.ok_or("missing label details")
.or_fail()?;
verify_that!(
label_details.detail.as_ref(),
some(eq(" Color(17, 34, 51, 255)"))
)?;
verify_that!(label_details.description.as_ref(), some(eq("Color")))?;
verify_that!(
item.data
.as_ref()
.and_then(|data| data.get("color"))
.and_then(|color| color.get("hex"))
.and_then(|hex| hex.as_str()),
some(eq("#112233FF"))
)?;
verify_that!(
item.data
.as_ref()
.and_then(|data| data.get("color"))
.and_then(|color| color.get("rgb"))
.and_then(|rgb| rgb.as_str()),
some(eq("rgb(17, 34, 51)"))
)?;
Ok(())
}
#[gtest]
fn test_gmod_color_member_completion_includes_preview_metadata() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
---@class Color
---@return Color
function Color(r, g, b, a) end
local SKIN = {}
SKIN.HeaderColor = Color(20, 40, 60, 128)
SKIN.<??>
"#,
)?;
let file_id = ws.def(&content);
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
let item = items
.into_iter()
.find(|item| item.label == "HeaderColor")
.ok_or("missing HeaderColor completion")
.or_fail()?;
verify_eq!(item.kind, Some(CompletionItemKind::COLOR))?;
let label_details = item
.label_details
.as_ref()
.ok_or("missing label details")
.or_fail()?;
verify_that!(
label_details.detail.as_ref(),
some(eq(" Color(20, 40, 60, 128)"))
)?;
verify_that!(label_details.description.as_ref(), some(eq("Color")))?;
verify_that!(
item.data
.as_ref()
.and_then(|data| data.get("color"))
.and_then(|color| color.get("rgba"))
.and_then(|rgba| rgba.as_str()),
some(eq("rgba(20, 40, 60, 128)"))
)?;
verify_that!(
item.data
.as_ref()
.and_then(|data| data.get("color"))
.and_then(|color| color.get("gmod"))
.and_then(|gmod| gmod.as_str()),
some(eq("Color(20, 40, 60, 128)"))
)?;
Ok(())
}
#[gtest]
fn test_gmod_constructor_completion_includes_literal_detail() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@class Vector
---@return Vector
function Vector(x, y, z) end
local SPAWN_POS = Vector(-200, 0, 50)
SPAWN<??>
"#,
vec![VirtualCompletionItem {
label: "SPAWN_POS".to_string(),
kind: CompletionItemKind::STRUCT,
label_detail: Some(" = Vector(-200, 0, 50)".to_string()),
}],
));
check!(ws.check_completion(
r#"
---@class Angle
---@return Angle
function Angle(p, y, r) end
local CAMERA_ANGLE = Angle(10.5, 180, 0)
CAMERA<??>
"#,
vec![VirtualCompletionItem {
label: "CAMERA_ANGLE".to_string(),
kind: CompletionItemKind::STRUCT,
label_detail: Some(" = Angle(10.5, 180, 0)".to_string()),
}],
));
Ok(())
}
#[gtest]
fn test_gmod_constructor_member_completion_includes_literal_detail() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@class Vector
---@return Vector
function Vector(x, y, z) end
---@class Angle
---@return Angle
function Angle(p, y, r) end
local ENT = {}
ENT.CameraOffset = Vector(-200, 0, 50)
ENT.SpawnAngle = Angle(0, 180, 0)
ENT.<??>
"#,
vec![
VirtualCompletionItem {
label: "CameraOffset".to_string(),
kind: CompletionItemKind::STRUCT,
label_detail: Some(" = Vector(-200, 0, 50)".to_string()),
},
VirtualCompletionItem {
label: "SpawnAngle".to_string(),
kind: CompletionItemKind::STRUCT,
label_detail: Some(" = Angle(0, 180, 0)".to_string()),
},
],
));
Ok(())
}
#[gtest]
fn test_dynamic_gmod_constructor_completion_does_not_include_literal_detail() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_completion(
r#"
---@class Vector
---@return Vector
function Vector(x, y, z) end
local x = 1
local DYNAMIC_POS = Vector(x, 0, 50)
DYNAMIC<??>
"#,
vec![VirtualCompletionItem {
label: "DYNAMIC_POS".to_string(),
kind: CompletionItemKind::STRUCT,
label_detail: None,
}],
));
Ok(())
}
#[gtest]
fn test_gmod_include_path_completion_filters_to_lua_files() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
let root = create_path_completion_root("gmod_include_path")?;
fs::create_dir_all(root.join("lua/includes")).or_fail()?;
fs::write(root.join("lua/includes/init.lua"), "").or_fail()?;
fs::write(root.join("lua/includes/icon.png"), "").or_fail()?;
ws.analysis.add_main_workspace(root.clone());
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
include("lua/includes/<??>")
"#,
)?;
let file_id = define_disk_file(&mut ws, root.join("lua/autorun/test.lua"), &content)?;
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
verify_that!(items.iter().any(|item| item.label == "init.lua"), eq(true))?;
verify_that!(items.iter().any(|item| item.label == "icon.png"), eq(false))
}
#[gtest]
fn test_member_named_include_uses_annotation_path_kind() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let root = create_path_completion_root("member_include_path")?;
fs::create_dir_all(root.join("materials/icons")).or_fail()?;
fs::write(root.join("materials/icons/icon.png"), "").or_fail()?;
fs::write(root.join("materials/icons/cl_init.lua"), "").or_fail()?;
ws.analysis.add_main_workspace(root.clone());
ws.def(
r#"
---@class Loader
---@field include fun(self: Loader, path: Path)
---@type Loader
local loader
"#,
);
let (content, position) = ProviderVirtualWorkspace::handle_file_content(
r#"
loader:include("materials/icons/<??>")
"#,
)?;
let file_id = define_disk_file(&mut ws, root.join("lua/autorun/test.lua"), &content)?;
let result = completion(
&ws.analysis,
file_id,
position,
CompletionTriggerKind::INVOKED,
CancellationToken::new(),
)
.ok_or("failed to get completion")
.or_fail()?;
let items = match result {
CompletionResponse::Array(items) => items,
CompletionResponse::List(list) => list.items,
};
verify_that!(items.iter().any(|item| item.label == "icon.png"), eq(true))?;
verify_that!(
items.iter().any(|item| item.label == "cl_init.lua"),
eq(true)
)
}
fn create_path_completion_root(name: &str) -> Result<PathBuf> {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.or_fail()?
.as_nanos();
let root = std::env::temp_dir().join(format!("glua_ls_{name}_{nonce}"));
fs::create_dir_all(&root).or_fail()?;
Ok(root)
}
fn define_disk_file(
ws: &mut ProviderVirtualWorkspace,
path: PathBuf,
content: &str,
) -> Result<FileId> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).or_fail()?;
}
let uri = file_path_to_uri(&path)
.ok_or("failed to create file URI")
.or_fail()?;
ws.analysis
.update_file_by_uri(&uri, Some(content.to_string()))
.or_fail()
}
}