crabsoup_mlua_analyze/
lib.rs1extern crate crabsoup_luau_sys;
2
3use std::{
4 ffi::{c_char, c_uint, c_void},
5 mem,
6};
7
8#[repr(C)]
9struct RustString {
10 data: *const c_char,
11 len: usize,
12}
13impl RustString {
14 unsafe fn decode(&self) -> String {
15 let slice =
16 std::str::from_utf8(std::slice::from_raw_parts(self.data as *const _, self.len))
17 .expect("Received invalid UTF-8 from Luau?");
18 slice.to_string()
19 }
20 unsafe fn encode(str: &str) -> Self {
21 RustString { data: str.as_ptr() as *const _, len: str.len() }
22 }
23}
24
25#[repr(C)]
26struct RustLineColumn {
27 line: c_uint,
28 column: c_uint,
29}
30
31#[derive(Debug)]
32struct RustCheckResultReceiver {
33 results: Vec<AnalyzeResult>,
34}
35
36#[derive(Clone, Debug)]
37pub struct AnalyzeResult {
38 pub location: String,
39 pub location_start: AnalyzeLocation,
40 pub location_end: AnalyzeLocation,
41 pub is_error: bool,
42 pub is_lint: bool,
43 pub message: String,
44}
45
46#[derive(Copy, Clone, Debug)]
47pub struct AnalyzeLocation {
48 pub line: usize,
49 pub column: usize,
50}
51impl From<RustLineColumn> for AnalyzeLocation {
52 fn from(value: RustLineColumn) -> Self {
53 AnalyzeLocation { line: value.line as usize, column: value.column as usize }
54 }
55}
56
57#[repr(transparent)]
58struct FrontendWrapper(c_void);
59
60extern "C-unwind" {
61 fn luauAnalyze_new_frontend() -> *mut FrontendWrapper;
62 fn luauAnalyze_register_definitions(
63 wrapper: *mut FrontendWrapper,
64 module_name: RustString,
65 definitions: RustString,
66 ) -> bool;
67 fn luauAnalyze_set_deprecation(
68 wrapper: *mut FrontendWrapper,
69 module_path: RustString,
70 replacement: RustString,
71 ) -> bool;
72 fn luauAnalyze_freeze_definitions(wrapper: *mut FrontendWrapper);
73 #[allow(improper_ctypes)] fn luauAnalyze_check(
75 receiver: *mut RustCheckResultReceiver,
76 wrapper: *mut FrontendWrapper,
77 name: RustString,
78 contents: RustString,
79 is_module: bool,
80 );
81 fn luauAnalyze_free_frontend(wrapper: *mut FrontendWrapper);
82}
83
84mod exports {
85 use super::*;
86
87 #[no_mangle]
88 pub unsafe extern "C-unwind" fn luauAnalyze_push_result(
89 receiver: *mut RustCheckResultReceiver,
90 module: RustString,
91 error_start: RustLineColumn,
92 error_end: RustLineColumn,
93 is_error: bool,
94 is_lint: bool,
95 message: RustString,
96 ) {
97 (*receiver).results.push(AnalyzeResult {
98 location: module.decode(),
99 location_start: error_start.into(),
100 location_end: error_end.into(),
101 is_error,
102 is_lint,
103 message: message.decode(),
104 });
105 }
106}
107
108pub struct LuaAnalyzerBuilder {
109 underlying: *mut FrontendWrapper,
110}
111impl LuaAnalyzerBuilder {
112 pub fn new() -> Self {
113 LuaAnalyzerBuilder { underlying: unsafe { luauAnalyze_new_frontend() } }
114 }
115
116 pub fn add_definitions(&mut self, name: &str, definitions: &str) {
117 if !unsafe {
118 luauAnalyze_register_definitions(
119 self.underlying,
120 RustString::encode(name),
121 RustString::encode(definitions),
122 )
123 } {
124 panic!("Failed to parse definitions file: {name}");
125 }
126 }
127
128 pub fn set_deprecation(&mut self, name: &str, replacement: Option<&str>) {
129 let replacement = replacement.unwrap_or("");
130 if !unsafe {
131 luauAnalyze_set_deprecation(
132 self.underlying,
133 RustString::encode(name),
134 RustString::encode(replacement),
135 )
136 } {
137 panic!("set_deprecation failed");
138 }
139 }
140
141 pub fn build(self) -> LuaAnalyzer {
142 unsafe { luauAnalyze_freeze_definitions(self.underlying) }
143 let out = LuaAnalyzer { underlying: self.underlying };
144 mem::forget(self);
145 out
146 }
147}
148impl Drop for LuaAnalyzerBuilder {
149 fn drop(&mut self) {
150 unsafe { luauAnalyze_free_frontend(self.underlying) }
151 }
152}
153
154pub struct LuaAnalyzer {
155 underlying: *mut FrontendWrapper,
156}
157impl LuaAnalyzer {
158 pub fn check(&self, name: &str, contents: &str, is_module: bool) -> Vec<AnalyzeResult> {
159 let mut receiver = RustCheckResultReceiver { results: vec![] };
160 unsafe {
161 luauAnalyze_check(
162 &mut receiver,
163 self.underlying,
164 RustString::encode(name),
165 RustString::encode(contents),
166 is_module,
167 );
168 }
169 receiver.results
170 }
171}
172impl Drop for LuaAnalyzer {
173 fn drop(&mut self) {
174 unsafe { luauAnalyze_free_frontend(self.underlying) }
175 }
176}