luaur_analysis/records/
lint_unused_function.rs1use 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 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}