gobject_linter/rules/
use_g_object_new_with_properties.rs1use gobject_ast::model::{
2 CallExpression, Expression, FileModel, FunctionDefItem, SourceLocation, Statement,
3};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Rule, Violation},
9};
10
11pub struct UseGObjectNewWithProperties;
12
13impl Rule for UseGObjectNewWithProperties {
14 fn name(&self) -> &'static str {
15 "use_g_object_new_with_properties"
16 }
17
18 fn description(&self) -> &'static str {
19 "Suggest setting properties in g_object_new instead of separate g_object_set calls"
20 }
21
22 fn category(&self) -> crate::rules::Category {
23 crate::rules::Category::Complexity
24 }
25
26 fn check_func_impl(
27 &self,
28 _ast_context: &AstContext,
29 _config: &Config,
30 func: &FunctionDefItem,
31 file: &FileModel,
32 violations: &mut Vec<Violation>,
33 ) {
34 let empty_new_calls: Vec<_> = func
36 .find_calls(&["g_object_new"])
37 .into_iter()
38 .filter(|call| self.is_g_object_new_empty(call))
39 .collect();
40
41 if empty_new_calls.is_empty() {
42 return;
43 }
44
45 self.check_statements(&func.body_statements, &empty_new_calls, file, violations);
47 }
48}
49
50impl UseGObjectNewWithProperties {
51 fn check_statements(
52 &self,
53 statements: &[Statement],
54 empty_new_calls: &[&CallExpression],
55 file: &FileModel,
56 violations: &mut Vec<Violation>,
57 ) {
58 for i in 0..statements.len() {
59 if let Some((var_name, location)) =
61 self.find_empty_new_in_statement(&statements[i], empty_new_calls)
62 {
63 let mut set_count = 0;
65
66 for next_stmt in statements.iter().skip(i + 1) {
67 if let Some(set_var) = self.extract_g_object_set(next_stmt)
68 && set_var == var_name
69 {
70 set_count += 1;
71 continue;
72 }
73
74 break;
76 }
77
78 if set_count > 0 {
80 violations.push(self.violation_at(
81 &file.path,
82 location,
83 format!(
84 "Set properties in g_object_new() instead of {} separate g_object_set() call{}",
85 set_count,
86 if set_count > 1 { "s" } else { "" }
87 ),
88 ));
89 }
90 }
91
92 statements[i].for_each_child_block(|body| {
93 self.check_statements(body, empty_new_calls, file, violations);
94 });
95 }
96 }
97
98 fn find_empty_new_in_statement<'a>(
101 &self,
102 stmt: &'a Statement,
103 empty_new_calls: &[&CallExpression],
104 ) -> Option<(&'a str, &'a SourceLocation)> {
105 match stmt {
106 Statement::Declaration(decl) => {
108 if let Some(Expression::Call(call)) = &decl.initializer {
109 for empty_call in empty_new_calls {
111 if call.location.start_byte == empty_call.location.start_byte {
112 return Some((decl.name.as_str(), &decl.location));
113 }
114 }
115 }
116 }
117 Statement::Expression(expr_stmt) => {
119 if let Expression::Assignment(assign) = expr_stmt.as_ref()
120 && let Expression::Call(call) = &*assign.rhs
121 {
122 for empty_call in empty_new_calls {
123 if call.location.start_byte == empty_call.location.start_byte {
124 let var_name = assign.lhs_as_text();
125 if !var_name.is_empty() {
126 return Some((var_name, expr_stmt.location()));
127 }
128 }
129 }
130 }
131 }
132 _ => {}
133 }
134
135 None
136 }
137
138 fn is_g_object_new_empty(&self, call: &CallExpression) -> bool {
141 if !call.is_function("g_object_new") {
142 return false;
143 }
144
145 match call.arguments.len() {
149 1 => true,
150 2 => call.arguments[1].is_null(),
151 _ => false,
152 }
153 }
154
155 fn extract_g_object_set(&self, stmt: &Statement) -> Option<String> {
157 let Statement::Expression(expr_stmt) = stmt else {
158 return None;
159 };
160
161 let Expression::Call(call) = expr_stmt.as_ref() else {
162 return None;
163 };
164
165 if !call.is_function("g_object_set") {
166 return None;
167 }
168
169 let expr = call.get_arg(0)?;
171 expr.extract_variable_name()
172 .map(std::string::ToString::to_string)
173 }
174}