#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::handlers::references::{references, search_decl_usages};
use crate::handlers::test_lib::{ProviderVirtualWorkspace, VirtualLocation, check};
use glua_code_analysis::{Emmyrc, LuaDeclId};
use glua_parser::{LuaAstNode, LuaLocalFuncStat, LuaLocalName};
use googletest::prelude::*;
use tokio_util::sync::CancellationToken;
#[gtest]
fn test_function_references() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_references(
r#"
local export = {}
local function fl<??>ush()
end
export.flush = flush
return export
"#,
vec![(
"1.lua",
r#"
local flush = require("virtual_0").flush
flush()
"#,
)],
vec![
VirtualLocation {
file: "".to_string(),
line: 2,
},
VirtualLocation {
file: "".to_string(),
line: 4,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 2,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "".to_string(),
line: 4,
},
]
));
Ok(())
}
#[gtest]
fn test_function_references_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_references(
r#"
local function fl<??>ush()
end
return {
flush = flush,
}
"#,
vec![(
"1.lua",
r#"
local flush = require("virtual_0").flush
flush()
"#,
)],
vec![
VirtualLocation {
file: "".to_string(),
line: 1,
},
VirtualLocation {
file: "".to_string(),
line: 4,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 2,
},
VirtualLocation {
file: "1.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "".to_string(),
line: 4,
},
]
));
Ok(())
}
#[gtest]
fn test_decl_usages_excludes_forward_declaration_and_function_definition() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let file_id = ws.def(
r#"
local create_initial_simplex4
function create_initial_simplex4(points, thread_yield)
return { points, thread_yield }
end
local faces = create_initial_simplex4({}, nil)
"#,
);
let semantic_model = check!(ws.analysis.compilation.get_semantic_model(file_id));
let local_name = check!(
semantic_model
.get_root()
.descendants::<LuaLocalName>()
.next()
);
let decl_id = LuaDeclId::new(file_id, local_name.get_position());
let mut results = Vec::new();
check!(search_decl_usages(
&ws.analysis.compilation,
decl_id,
&mut results,
&CancellationToken::new()
));
assert_that!(results.len(), eq(1));
assert_that!(results[0].range.start.line, eq(7));
Ok(())
}
#[gtest]
fn test_decl_usages_traverses_module_alias_without_counting_alias_binding() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let module_file_id = ws.def_file(
"a.lua",
r#"
local function init()
end
return init
"#,
);
ws.def_file(
"consumer.lua",
r#"
local init = require("a")
init()
"#,
);
let semantic_model = check!(ws.analysis.compilation.get_semantic_model(module_file_id));
let local_func = check!(
semantic_model
.get_root()
.descendants::<LuaLocalFuncStat>()
.next()
);
let local_name = check!(local_func.get_local_name());
let decl_id = LuaDeclId::new(module_file_id, local_name.get_position());
let mut results = Vec::new();
check!(search_decl_usages(
&ws.analysis.compilation,
decl_id,
&mut results,
&CancellationToken::new()
));
let consumer_lines = results
.iter()
.filter(|location| location.uri.to_string().contains("consumer.lua"))
.map(|location| location.range.start.line)
.collect::<HashSet<_>>();
assert_that!(consumer_lines.contains(&2), eq(true));
assert_that!(consumer_lines.contains(&1), eq(false));
Ok(())
}
#[gtest]
fn test_module_return() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_references(
r#"
local function init()
end
return in<??>it
"#,
vec![(
"a.lua",
r#"
local init = require("virtual_0")
init()
"#,
)],
vec![
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "a.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "a.lua".to_string(),
line: 2,
},
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 3,
},
],
));
Ok(())
}
#[gtest]
fn test_module_return_2() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
ws.def_file(
"a.lua",
r#"
local function getA()
end
return {
getA = getA
}
"#,
);
check!(ws.check_references(
r#"
local AModule = require("a")
AMo<??>dule.getA()
"#,
vec![],
vec![
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 2,
},
],
));
Ok(())
}
#[gtest]
fn test_member_references_alias_cycle_does_not_stack_overflow() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (main_content, position) = check!(ProviderVirtualWorkspace::handle_file_content(
r#"
local t = {}
t.m<??> = function() end
local x = t.m
t.m = x
"#,
));
let file_id = ws.def(&main_content);
let result = references(
&ws.analysis,
file_id,
position,
&CancellationToken::new(),
true,
)
.ok_or("failed to get references")
.or_fail()?;
let lines: HashSet<u32> = result.iter().map(|l| l.range.start.line).collect();
assert!(lines.contains(&2));
assert!(lines.contains(&3));
assert!(lines.contains(&4));
Ok(())
}
#[gtest]
fn test_gmod_vgui_panel_string_references() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
check!(ws.check_references(
r#"
local parent = vgui.Create("MyPa<??>nel")
parent:Add("MyPanel")
"#,
vec![
(
"defs.lua",
"\n\n\n\n\n\n\n\nvgui.Register(\"MyPanel\", PANEL, \"DPanel\")\n",
),
(
"usage.lua",
"\n\n\n\n\n\n\n\n\n\n\n\nlocal created = vgui.Create(\"MyPanel\")\n",
),
],
vec![
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 4,
},
VirtualLocation {
file: "defs.lua".to_string(),
line: 8,
},
VirtualLocation {
file: "usage.lua".to_string(),
line: 12,
},
],
));
Ok(())
}
#[gtest]
fn test_gmod_net_message_string_references() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let mut emmyrc = Emmyrc::default();
emmyrc.gmod.enabled = true;
ws.analysis.update_config(emmyrc.into());
check!(ws.check_references(
r#"
net.Receive("MyMe<??>ssage", function() end)
"#,
vec![(
"send.lua",
"\n\n\n\n\n\n\nnet.Start(\"MyMessage\")\nnet.Broadcast()\n",
)],
vec![
VirtualLocation {
file: "virtual_0.lua".to_string(),
line: 1,
},
VirtualLocation {
file: "send.lua".to_string(),
line: 7,
},
],
));
Ok(())
}
#[gtest]
fn test_member_variable_references_include_usages() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
check!(ws.check_references(
r#"
---@class MyClass
---@field myField number
---@type MyClass
local obj
obj.my<??>Field = 42
print(obj.myField)
"#,
vec![],
vec![
VirtualLocation {
file: "".to_string(),
line: 2,
},
VirtualLocation {
file: "".to_string(),
line: 7,
},
VirtualLocation {
file: "".to_string(),
line: 8,
},
],
));
Ok(())
}
#[gtest]
fn test_member_variable_references_exclude_declaration_when_flag_false() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (main_content, position) = check!(ProviderVirtualWorkspace::handle_file_content(
r#"
---@class MyClass
---@field myField number
---@type MyClass
local obj
obj.my<??>Field = 42
print(obj.myField)
"#,
));
let file_id = ws.def(&main_content);
let result_with_decl = references(
&ws.analysis,
file_id,
position.clone(),
&CancellationToken::new(),
true,
)
.ok_or("failed to get references")
.or_fail()?;
let result_without_decl = references(
&ws.analysis,
file_id,
position,
&CancellationToken::new(),
false,
)
.ok_or("failed to get references")
.or_fail()?;
let lines_with_decl: HashSet<u32> = result_with_decl
.iter()
.map(|l| l.range.start.line)
.collect();
assert!(
lines_with_decl.contains(&2),
"expected @field definition (line 2) with include_declaration=true, got lines: {lines_with_decl:?}"
);
let lines_without_decl: HashSet<u32> = result_without_decl
.iter()
.map(|l| l.range.start.line)
.collect();
assert!(
!lines_without_decl.contains(&2),
"expected @field definition (line 2) to be excluded with include_declaration=false, got lines: {lines_without_decl:?}"
);
assert!(
lines_without_decl.contains(&7),
"expected usage site (line 7) with include_declaration=false"
);
assert!(
lines_without_decl.contains(&8),
"expected usage site (line 8) with include_declaration=false"
);
Ok(())
}
#[gtest]
fn test_dynamic_key_table_field_references_include_key_source() -> 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());
check!(ws.check_references(
r#"
local rec = { data = {} }
local data = rec.data
for key, value in pairs({ forwardSlip = 1, sideSlip = 2 }) do
data[key] = value
end
local d = rec.data
math.abs(d.forw<??>ardSlip or 0)
print(d.forwardSlip)
"#,
vec![],
vec![
VirtualLocation {
file: "".to_string(),
line: 3,
},
VirtualLocation {
file: "".to_string(),
line: 7,
},
VirtualLocation {
file: "".to_string(),
line: 8,
},
],
));
Ok(())
}
#[gtest]
fn test_global_references_exclude_declaration_when_flag_false() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (main_content, position) = check!(ProviderVirtualWorkspace::handle_file_content(
r#"
MyGl<??>obal = 42
print(MyGlobal)
MyGlobal = 99
"#,
));
let file_id = ws.def(&main_content);
let result_with_decl = references(
&ws.analysis,
file_id,
position.clone(),
&CancellationToken::new(),
true,
)
.ok_or("failed to get references")
.or_fail()?;
let result_without_decl = references(
&ws.analysis,
file_id,
position,
&CancellationToken::new(),
false,
)
.ok_or("failed to get references")
.or_fail()?;
let lines_with_decl: HashSet<u32> = result_with_decl
.iter()
.map(|l| l.range.start.line)
.collect();
let lines_without_decl: HashSet<u32> = result_without_decl
.iter()
.map(|l| l.range.start.line)
.collect();
assert!(
lines_with_decl.contains(&1),
"expected declaration (line 1) with include_declaration=true, got lines: {lines_with_decl:?}"
);
assert!(
!lines_without_decl.contains(&1),
"expected declaration (line 1) to be excluded with include_declaration=false, got lines: {lines_without_decl:?}"
);
assert!(
lines_without_decl.contains(&2),
"expected usage (line 2) with include_declaration=false"
);
assert!(
lines_without_decl.contains(&3),
"expected write usage (line 3) with include_declaration=false"
);
Ok(())
}
#[gtest]
fn test_member_references_same_line_decl_and_usage() -> Result<()> {
let mut ws = ProviderVirtualWorkspace::new();
let (main_content, position) = check!(ProviderVirtualWorkspace::handle_file_content(
r#"
---@class MyClass
---@field myField number
---@type MyClass
local obj
obj.my<??>Field = 42; print(obj.myField)
"#,
));
let file_id = ws.def(&main_content);
let result_without_decl = references(
&ws.analysis,
file_id,
position,
&CancellationToken::new(),
false,
)
.ok_or("failed to get references")
.or_fail()?;
let lines_without_decl: HashSet<u32> = result_without_decl
.iter()
.map(|l| l.range.start.line)
.collect();
assert!(
!lines_without_decl.contains(&2),
"expected @field definition (line 2) to be excluded with include_declaration=false, got lines: {lines_without_decl:?}"
);
assert!(
lines_without_decl.contains(&7),
"expected usage site (line 7) with include_declaration=false, got lines: {lines_without_decl:?}"
);
let same_line_ranges: HashSet<_> = result_without_decl
.iter()
.filter(|location| location.range.start.line == 7)
.map(|location| {
(
location.range.start.line,
location.range.start.character,
location.range.end.line,
location.range.end.character,
)
})
.collect();
assert_eq!(
same_line_ranges.len(),
2,
"expected two distinct same-line member references on line 7 (write + read), got: {result_without_decl:?}"
);
Ok(())
}
}