gobject_linter/rules/
use_g_bytes_unref_to_data.rs1use gobject_ast::model::{
2 Assignment, AssignmentOp, CallExpression, Expression, FileModel, FunctionDefItem, Statement,
3};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Fix, Rule, Violation},
9};
10
11pub struct UseGBytesUnrefToData;
12
13impl Rule for UseGBytesUnrefToData {
14 fn name(&self) -> &'static str {
15 "use_g_bytes_unref_to_data"
16 }
17
18 fn description(&self) -> &'static str {
19 "Use g_bytes_unref_to_data() instead of g_bytes_get_data() + g_bytes_unref()"
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, 32))
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 |stmt1, stmt2| {
43 self.try_bytes_pattern(stmt1, stmt2, file, config, violations);
44 });
45 }
46}
47
48impl UseGBytesUnrefToData {
49 fn try_bytes_pattern(
51 &self,
52 stmt1: &Statement,
53 stmt2: &Statement,
54 file: &FileModel,
55 config: &Config,
56 violations: &mut Vec<Violation>,
57 ) {
58 let Some((dest, bytes_var, size_arg, assignment, _call1)) =
60 self.extract_bytes_get_data(stmt1)
61 else {
62 return;
63 };
64
65 if self.extract_bytes_unref(stmt2, bytes_var).is_none() {
67 return;
68 };
69
70 let call = config
72 .style
73 .format_call("g_bytes_unref_to_data", &[bytes_var, size_arg]);
74 let replacement = format!("{} = {};", dest, call);
75 let message = format!(
76 "Use g_bytes_unref_to_data({}, {}) instead of g_bytes_get_data() followed by g_bytes_unref()",
77 bytes_var, size_arg
78 );
79 let stmt1_end = stmt1.location().find_semicolon_end();
81 let fixes = vec![
82 Fix::new(stmt1.location().start_byte, stmt1_end, replacement),
84 Fix::delete_line(stmt2.location()),
86 ];
87
88 violations.push(self.violation_with_fixes_at(
89 &file.path,
90 &assignment.location,
91 message,
92 fixes,
93 ));
94 }
95
96 fn extract_bytes_get_data<'a>(
99 &self,
100 stmt: &'a Statement,
101 ) -> Option<(
102 &'a str,
103 &'a str,
104 &'a str,
105 &'a Assignment,
106 &'a CallExpression,
107 )> {
108 let Statement::Expression(expr_stmt) = stmt else {
109 return None;
110 };
111 let Expression::Assignment(assignment) = expr_stmt.as_ref() else {
112 return None;
113 };
114
115 if assignment.operator != AssignmentOp::Assign {
117 return None;
118 }
119
120 let Expression::Call(call) = assignment.rhs.as_ref() else {
122 return None;
123 };
124
125 if !call.is_function("g_bytes_get_data") {
126 return None;
127 }
128
129 if call.arguments.len() != 2 {
131 return None;
132 }
133
134 let bytes_var = call.get_arg_text(0)?;
136 let size_arg = call.get_arg_text(1)?;
137
138 let dest_var = assignment.lhs_as_text();
139 if dest_var.is_empty() {
140 return None;
141 }
142
143 Some((dest_var, bytes_var, size_arg, assignment, call))
144 }
145
146 fn extract_bytes_unref(&self, stmt: &Statement, expected_var: &str) -> Option<usize> {
149 let Statement::Expression(expr_stmt) = stmt else {
150 return None;
151 };
152
153 let Expression::Call(call) = expr_stmt.as_ref() else {
154 return None;
155 };
156
157 if !call.is_function("g_bytes_unref") {
158 return None;
159 }
160
161 if call.arguments.len() != 1 {
163 return None;
164 }
165
166 let arg_text = call.get_arg_text(0)?;
168 if arg_text != expected_var {
169 return None;
170 }
171
172 Some(expr_stmt.location().find_semicolon_end())
173 }
174}