luaur_analysis/methods/
lint_table_operations_check_table_call.rs1use 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}