gobject_linter/rules/
use_g_set_object.rs1use gobject_ast::model::{
2 AssignmentOp, Expression, FileModel, FunctionDefItem, Statement, UnaryOp,
3};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Fix, Rule, Violation},
9};
10
11pub struct UseGSetObject;
12
13impl Rule for UseGSetObject {
14 fn name(&self) -> &'static str {
15 "use_g_set_object"
16 }
17
18 fn description(&self) -> &'static str {
19 "Suggest g_set_object() instead of manual g_clear_object and g_object_ref"
20 }
21
22 fn category(&self) -> crate::rules::Category {
23 crate::rules::Category::Complexity
24 }
25
26 fn fixable(&self) -> bool {
27 true
28 }
29
30 fn min_glib_version(&self) -> Option<(u32, u32)> {
31 Some((2, 44))
32 }
33
34 fn check_func_impl(
35 &self,
36 _ast_context: &AstContext,
37 config: &Config,
38 func: &FunctionDefItem,
39 file: &FileModel,
40 violations: &mut Vec<Violation>,
41 ) {
42 Statement::walk_pairs(&func.body_statements, &mut |s1, s2| {
43 self.try_clear_then_ref(s1, s2, file, config, violations);
44 });
45 }
46}
47
48impl UseGSetObject {
49 fn try_clear_then_ref(
50 &self,
51 s1: &Statement,
52 s2: &Statement,
53 file: &FileModel,
54 config: &Config,
55 violations: &mut Vec<Violation>,
56 ) -> bool {
57 let Some((var_name, needs_deref)) = self.extract_clear_or_unref_var(s1) else {
59 return false;
60 };
61
62 let Some((assign_var, new_val)) = self.extract_object_ref_assignment(s2) else {
64 return false;
65 };
66
67 let matches = if needs_deref {
68 assign_var.len() == var_name.len() + 1
69 && assign_var.starts_with('*')
70 && assign_var[1..] == *var_name
71 } else {
72 assign_var == var_name
73 };
74 if !matches {
75 return false;
76 }
77
78 let replacement = if needs_deref {
82 config
83 .style
84 .format_call_stmt("g_set_object", &[var_name, new_val])
85 } else {
86 config
87 .style
88 .format_addr_call_stmt("g_set_object", var_name, &[new_val])
89 };
90
91 let s2_end = s2.location().find_semicolon_end();
93 let message = format!("Use {replacement} instead of g_clear_object and g_object_ref");
94 let fixes = vec![
95 Fix::delete_line(s1.location()),
97 Fix::new(s2.location().start_byte, s2_end, replacement),
99 ];
100
101 violations.push(self.violation_with_fixes_at(&file.path, s1.location(), message, fixes));
102 true
103 }
104
105 fn extract_clear_or_unref_var<'a>(&self, stmt: &'a Statement) -> Option<(&'a str, bool)> {
109 let Statement::Expression(expr_stmt) = stmt else {
110 return None;
111 };
112
113 let Expression::Call(call) = expr_stmt.as_ref() else {
114 return None;
115 };
116
117 if call.arguments.is_empty() {
118 return None;
119 }
120
121 if call.is_function("g_clear_object") {
122 let first_arg = call.get_arg(0)?;
126 if let Expression::Unary(unary) = first_arg
127 && unary.operator == UnaryOp::AddressOf
128 {
129 return Some((unary.operand.location().as_str()?, false));
131 } else {
132 return Some((first_arg.location().as_str()?, true));
134 }
135 } else if call.is_function("g_object_unref") {
136 let first_arg = call.get_arg(0)?;
138 return Some((first_arg.location().as_str()?, false));
139 }
140
141 None
142 }
143
144 fn extract_object_ref_assignment<'a>(&self, stmt: &'a Statement) -> Option<(&'a str, &'a str)> {
146 let Statement::Expression(expr_stmt) = stmt else {
147 return None;
148 };
149
150 let Expression::Assignment(assign) = expr_stmt.as_ref() else {
151 return None;
152 };
153
154 if assign.operator != AssignmentOp::Assign {
155 return None;
156 }
157
158 if let Expression::Call(call) = &*assign.rhs
160 && call.is_function("g_object_ref")
161 && !call.arguments.is_empty()
162 {
163 let new_val = call.get_arg(0)?.location().as_str()?;
164 let var_name = assign.lhs_as_text();
165 if !var_name.is_empty() {
166 return Some((var_name, new_val));
167 }
168 }
169
170 None
171 }
172}