Skip to main content

luaur_analysis/methods/
lint_table_operations_check_table_call.rs

1use crate::functions::emit_warning::emit_warning;
2use crate::functions::follow_type::follow_type_id;
3use crate::records::lint_table_operations::LintTableOperations;
4use luaur_ast::records::ast_expr_binary::{AstExprBinary, AstExprBinary_Op};
5use luaur_ast::records::ast_expr_call::AstExprCall;
6use luaur_ast::records::ast_expr_index_name::AstExprIndexName;
7use luaur_ast::records::ast_expr_table::AstExprTable;
8use luaur_ast::records::ast_expr_type_assertion::AstExprTypeAssertion;
9use luaur_ast::records::ast_node::AstNode;
10use luaur_config::enums::code::Code;
11
12impl LintTableOperations {
13    pub fn check_table_call(&mut self, node: *mut AstExprCall, func: *mut AstExprIndexName) {
14        let node_ref = unsafe { &*node };
15        let args = node_ref.args.as_slice();
16
17        if unsafe { (*func).index.operator_eq_c_char(c"insert".as_ptr()) }
18            && node_ref.args.size == 2
19        {
20            let tail =
21                unsafe { luaur_ast::rtti::ast_node_as::<AstExprCall>(args[1] as *mut AstNode) };
22
23            if !tail.is_null() {
24                if let Some(funty) = unsafe { (*self.context).get_type((*tail).func) } {
25                    let ret = self.get_return_count(unsafe { follow_type_id(funty) });
26
27                    if ret > 1 {
28                        emit_warning(
29                            unsafe { &mut *self.context },
30                            Code::Code_TableOperations,
31                            unsafe { (*tail).base.base.location },
32                            format_args!(
33                                "table.insert may change behavior if the call returns more than one result; consider adding parentheses around second argument"
34                            ),
35                        );
36                    }
37                }
38            }
39        }
40
41        if unsafe { (*func).index.operator_eq_c_char(c"insert".as_ptr()) }
42            && node_ref.args.size >= 3
43        {
44            if self.is_constant(args[1], 0.0) {
45                emit_warning(
46                    unsafe { &mut *self.context },
47                    Code::Code_TableOperations,
48                    unsafe { (*args[1]).base.location },
49                    format_args!(
50                        "table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?"
51                    ),
52                );
53            }
54
55            if self.is_length(args[1], args[0]) {
56                emit_warning(
57                    unsafe { &mut *self.context },
58                    Code::Code_TableOperations,
59                    unsafe { (*args[1]).base.location },
60                    format_args!(
61                        "table.insert will insert the value before the last element, which is likely a bug; consider removing the second argument or wrap it in parentheses to silence"
62                    ),
63                );
64            }
65
66            let add =
67                unsafe { luaur_ast::rtti::ast_node_as::<AstExprBinary>(args[1] as *mut AstNode) };
68            if !add.is_null()
69                && unsafe { (*add).op == AstExprBinary_Op::Add }
70                && self.is_length(unsafe { (*add).left }, args[0])
71                && self.is_constant(unsafe { (*add).right }, 1.0)
72            {
73                emit_warning(
74                    unsafe { &mut *self.context },
75                    Code::Code_TableOperations,
76                    unsafe { (*args[1]).base.location },
77                    format_args!(
78                        "table.insert will append the value to the table; consider removing the second argument for efficiency"
79                    ),
80                );
81            }
82        }
83
84        if unsafe { (*func).index.operator_eq_c_char(c"remove".as_ptr()) }
85            && node_ref.args.size >= 2
86        {
87            if self.is_constant(args[1], 0.0) {
88                emit_warning(
89                    unsafe { &mut *self.context },
90                    Code::Code_TableOperations,
91                    unsafe { (*args[1]).base.location },
92                    format_args!(
93                        "table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?"
94                    ),
95                );
96            }
97
98            let sub =
99                unsafe { luaur_ast::rtti::ast_node_as::<AstExprBinary>(args[1] as *mut AstNode) };
100            if !sub.is_null()
101                && unsafe { (*sub).op == AstExprBinary_Op::Sub }
102                && self.is_length(unsafe { (*sub).left }, args[0])
103                && self.is_constant(unsafe { (*sub).right }, 1.0)
104            {
105                emit_warning(
106                    unsafe { &mut *self.context },
107                    Code::Code_TableOperations,
108                    unsafe { (*args[1]).base.location },
109                    format_args!(
110                        "table.remove will remove the value before the last element, which is likely a bug; consider removing the second argument or wrap it in parentheses to silence"
111                    ),
112                );
113            }
114        }
115
116        if unsafe { (*func).index.operator_eq_c_char(c"move".as_ptr()) } && node_ref.args.size >= 4
117        {
118            if self.is_constant(args[1], 0.0) {
119                emit_warning(
120                    unsafe { &mut *self.context },
121                    Code::Code_TableOperations,
122                    unsafe { (*args[1]).base.location },
123                    format_args!(
124                        "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"
125                    ),
126                );
127            } else if self.is_constant(args[3], 0.0) {
128                emit_warning(
129                    unsafe { &mut *self.context },
130                    Code::Code_TableOperations,
131                    unsafe { (*args[3]).base.location },
132                    format_args!(
133                        "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"
134                    ),
135                );
136            }
137        }
138
139        if unsafe { (*func).index.operator_eq_c_char(c"create".as_ptr()) }
140            && node_ref.args.size == 2
141        {
142            if !unsafe { luaur_ast::rtti::ast_node_as::<AstExprTable>(args[1] as *mut AstNode) }
143                .is_null()
144            {
145                emit_warning(
146                    unsafe { &mut *self.context },
147                    Code::Code_TableOperations,
148                    unsafe { (*args[1]).base.location },
149                    format_args!(
150                        "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"
151                    ),
152                );
153            }
154
155            let assertion = unsafe {
156                luaur_ast::rtti::ast_node_as::<AstExprTypeAssertion>(args[1] as *mut AstNode)
157            };
158            if !assertion.is_null()
159                && !unsafe {
160                    luaur_ast::rtti::ast_node_as::<AstExprTable>((*assertion).expr as *mut AstNode)
161                }
162                .is_null()
163            {
164                emit_warning(
165                    unsafe { &mut *self.context },
166                    Code::Code_TableOperations,
167                    unsafe { (*(*assertion).expr).base.location },
168                    format_args!(
169                        "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"
170                    ),
171                );
172            }
173        }
174    }
175}