Skip to main content

luaur_analysis/records/
lint_unused_function.rs

1use luaur_ast::records::ast_expr_global::AstExprGlobal;
2use luaur_ast::records::ast_name::AstName;
3use luaur_ast::records::ast_node::AstNode;
4use luaur_ast::records::ast_stat_function::AstStatFunction;
5use luaur_ast::records::ast_visitor::AstVisitor;
6use luaur_ast::records::location::Location;
7use luaur_common::records::dense_hash_map::DenseHashMap;
8use luaur_common::records::dense_hash_table::DenseDefault;
9use luaur_config::enums::code::Code;
10
11use crate::functions::emit_warning::emit_warning;
12use crate::records::lint_context::LintContext;
13
14#[derive(Debug, Clone, Default)]
15pub struct Global {
16    pub(crate) location: Location,
17    pub(crate) function: bool,
18    pub(crate) used: bool,
19}
20
21impl DenseDefault for Global {
22    fn dense_default() -> Self {
23        Self::default()
24    }
25}
26
27#[derive(Debug, Clone)]
28pub struct LintUnusedFunction {
29    pub(crate) context: *mut LintContext,
30    pub(crate) globals: DenseHashMap<AstName, Global>,
31}
32
33impl LintUnusedFunction {
34    pub fn new() -> Self {
35        Self {
36            context: core::ptr::null_mut(),
37            globals: DenseHashMap::new(AstName::new()),
38        }
39    }
40
41    pub fn process(&mut self, context: &mut LintContext) {
42        self.context = context as *mut LintContext;
43        // SAFETY: The visitor pattern requires a valid AST root.
44        // The context is guaranteed to be valid for the duration of the visit.
45        unsafe {
46            let root = (*self.context).root;
47            luaur_ast::visit::ast_stat_visit(root, self);
48        }
49        self.report();
50    }
51
52    pub fn report(&mut self) {
53        for (name, global) in self.globals.iter() {
54            if global.function && !global.used {
55                let c_str = name.value;
56                if !c_str.is_null() {
57                    let first_char = unsafe { *c_str };
58                    if first_char != b'_' as i8 {
59                        let name = unsafe { core::ffi::CStr::from_ptr(c_str).to_string_lossy() };
60                        emit_warning(
61                            unsafe { &mut *self.context },
62                            Code::Code_FunctionUnused,
63                            global.location,
64                            format_args!(
65                                "Function '{}' is never used; prefix with '_' to silence",
66                                name
67                            ),
68                        );
69                    }
70                }
71            }
72        }
73    }
74
75    pub fn visit_stat_function(&mut self, node: *mut core::ffi::c_void) -> bool {
76        let node = node as *mut AstStatFunction;
77        unsafe {
78            let name_expr = (*node).name;
79            let expr = luaur_ast::rtti::ast_node_as::<AstExprGlobal>(name_expr as *mut AstNode);
80            if !expr.is_null() {
81                let g = self.globals.get_or_insert((*expr).name);
82                g.function = true;
83                g.location = (*expr).base.base.location;
84                luaur_ast::visit::ast_expr_visit(
85                    (*node).func as *mut luaur_ast::records::ast_expr::AstExpr,
86                    self,
87                );
88                return false;
89            }
90        }
91        true
92    }
93
94    pub fn visit_expr_global(&mut self, node: *mut core::ffi::c_void) -> bool {
95        let node = node as *mut AstExprGlobal;
96        unsafe {
97            let g = self.globals.get_or_insert((*node).name);
98            g.used = true;
99        }
100        true
101    }
102}
103
104impl AstVisitor for LintUnusedFunction {
105    fn visit_stat_function(&mut self, node: *mut core::ffi::c_void) -> bool {
106        self.visit_stat_function(node)
107    }
108
109    fn visit_expr_global(&mut self, node: *mut core::ffi::c_void) -> bool {
110        self.visit_expr_global(node)
111    }
112
113    fn visit_node(&mut self, _node: *mut core::ffi::c_void) -> bool {
114        true
115    }
116
117    fn visit_type(&mut self, _node: *mut core::ffi::c_void) -> bool {
118        false
119    }
120
121    fn visit_type_pack(&mut self, _node: *mut core::ffi::c_void) -> bool {
122        false
123    }
124}