1use mlua::prelude::*;
8
9use crate::config::LintConfig;
10use crate::engine::LintEngine;
11use crate::symbols::SymbolTable;
12use crate::types::LintResult;
13
14pub fn collect_symbols(lua: &Lua) -> LuaResult<SymbolTable> {
23 let mut symbols = SymbolTable::new();
24 let globals = lua.globals();
25
26 for pair in globals.pairs::<String, LuaValue>() {
27 let (key, value) = pair?;
28 symbols.add_global(&key);
29
30 if let LuaValue::Table(tbl) = value {
32 for field_pair in tbl.pairs::<String, LuaValue>() {
33 let (field_key, _) = field_pair?;
34 symbols.add_global_field(&key, &field_key);
35 }
36 }
37 }
38
39 Ok(symbols)
40}
41
42pub fn register(lua: &Lua) -> LuaResult<LintEngine> {
63 register_with_config(lua, LintConfig::default())
64}
65
66pub fn register_with_config(lua: &Lua, config: LintConfig) -> LuaResult<LintEngine> {
68 let symbols = collect_symbols(lua)?;
69 let mut engine = LintEngine::with_config(config);
70 *engine.symbols_mut() = symbols;
71 Ok(engine)
72}
73
74pub fn run_lint(code: &str, chunk_name: &str) -> Result<LintResult, String> {
83 let lua = Lua::new();
84 let engine = register(&lua).map_err(|e| format!("Failed to collect VM symbols: {e}"))?;
85 Ok(engine.lint(code, chunk_name))
86}
87
88pub fn lint(lua: &Lua, code: &str, chunk_name: &str) -> LuaResult<LintResult> {
104 let engine = register(lua)?;
105 Ok(engine.lint(code, chunk_name))
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn collect_symbols_includes_stdlib() {
114 let lua = Lua::new();
115 let symbols = collect_symbols(&lua).unwrap();
116 assert!(symbols.has_global("print"));
118 assert!(symbols.has_global("table"));
119 assert!(symbols.has_global("string"));
120 assert!(symbols.has_global("math"));
121 }
122
123 #[test]
124 fn collect_symbols_includes_table_fields() {
125 let lua = Lua::new();
126 let symbols = collect_symbols(&lua).unwrap();
127 assert!(symbols.has_global_field("table", "insert"));
129 assert!(symbols.has_global_field("string", "format"));
130 assert!(symbols.has_global_field("math", "floor"));
131 }
132
133 #[test]
134 fn collect_symbols_includes_custom_globals() {
135 let lua = Lua::new();
136 let tbl = lua.create_table().unwrap();
137 tbl.set("llm", lua.create_function(|_, ()| Ok(())).unwrap())
138 .unwrap();
139 tbl.set("state", lua.create_function(|_, ()| Ok(())).unwrap())
140 .unwrap();
141 lua.globals().set("alc", tbl).unwrap();
142
143 let symbols = collect_symbols(&lua).unwrap();
144 assert!(symbols.has_global("alc"));
145 assert!(symbols.has_global_field("alc", "llm"));
146 assert!(symbols.has_global_field("alc", "state"));
147 }
148
149 #[test]
150 fn register_creates_working_engine() {
151 let lua = Lua::new();
152 let engine = register(&lua).unwrap();
153
154 let result = engine.lint("print('hello')", "@test.lua");
156 assert_eq!(result.diagnostics.len(), 0);
157
158 let result = engine.lint("unknown_func()", "@test.lua");
160 assert!(result.warning_count > 0);
161 }
162
163 #[test]
164 fn run_lint_detects_undefined() {
165 let result = run_lint("unknown_func()", "@test.lua").unwrap();
166 assert!(result.warning_count > 0);
167 assert!(result.diagnostics[0].message.contains("unknown_func"));
168 }
169
170 #[test]
171 fn run_lint_allows_stdlib() {
172 let result = run_lint("print(table.insert)", "@test.lua").unwrap();
173 assert_eq!(result.diagnostics.len(), 0);
174 }
175
176 #[test]
177 fn lint_with_custom_globals() {
178 let lua = Lua::new();
179 let tbl = lua.create_table().unwrap();
180 tbl.set("llm", lua.create_function(|_, ()| Ok(())).unwrap())
181 .unwrap();
182 lua.globals().set("alc", tbl).unwrap();
183
184 let result = lint(&lua, "alc.llm('hello')", "@test.lua").unwrap();
186 assert_eq!(result.diagnostics.len(), 0);
187
188 let result = lint(&lua, "alc.unknown('hello')", "@test.lua").unwrap();
190 assert!(result.diagnostics.len() > 0);
191 }
192}