gobject_linter/rules/
use_g_file_load_bytes.rs1use std::collections::HashSet;
2
3use gobject_ast::model::{Expression, FileModel, FunctionDefItem, Statement, UnaryOp};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Rule, Violation},
9};
10
11pub struct UseGFileLoadBytes;
12
13impl Rule for UseGFileLoadBytes {
14 fn name(&self) -> &'static str {
15 "use_g_file_load_bytes"
16 }
17
18 fn description(&self) -> &'static str {
19 "Suggest g_file_load_bytes/g_file_load_bytes_async instead of g_file_load_contents + g_bytes_new_take"
20 }
21
22 fn category(&self) -> crate::rules::Category {
23 crate::rules::Category::Complexity
24 }
25
26 fn fixable(&self) -> bool {
27 false }
29
30 fn min_glib_version(&self) -> Option<(u32, u32)> {
31 Some((2, 56))
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 self.check_function(func, file, violations);
43 }
44}
45
46impl UseGFileLoadBytes {
47 fn check_function(
48 &self,
49 func: &FunctionDefItem,
50 file: &FileModel,
51 violations: &mut Vec<Violation>,
52 ) {
53 let load_contents_vars = self.find_load_contents_vars(func);
55
56 self.find_bytes_new_take_violations(
58 &func.body_statements,
59 file,
60 &load_contents_vars,
61 violations,
62 );
63 }
64
65 fn find_load_contents_vars<'a>(&self, func: &'a FunctionDefItem) -> HashSet<&'a str> {
68 let mut result = HashSet::new();
69
70 for call in func.find_calls(&["g_file_load_contents", "g_file_load_contents_finish"]) {
72 if call.arguments.len() >= 6 {
75 if let Some(contents_var) = self.extract_pointer_var(&call.arguments[2]) {
77 result.insert(contents_var);
78 }
79 }
80 }
81
82 result
83 }
84
85 fn find_bytes_new_take_violations(
86 &self,
87 statements: &[Statement],
88 file: &FileModel,
89 load_contents_vars: &HashSet<&str>,
90 violations: &mut Vec<Violation>,
91 ) {
92 for stmt in statements {
93 stmt.walk(&mut |s| match s {
94 Statement::Expression(expr_stmt) => {
95 self.check_expr_for_bytes_new_take(
96 expr_stmt,
97 file,
98 load_contents_vars,
99 violations,
100 );
101 }
102 Statement::Declaration(decl) => {
103 if let Some(init) = &decl.initializer {
104 self.check_expr_for_bytes_new_take(
105 init,
106 file,
107 load_contents_vars,
108 violations,
109 );
110 }
111 }
112 Statement::Return(ret) => {
113 if let Some(expr) = &ret.value {
114 self.check_expr_for_bytes_new_take(
115 expr,
116 file,
117 load_contents_vars,
118 violations,
119 );
120 }
121 }
122 _ => {}
123 });
124 }
125 }
126
127 fn check_expr_for_bytes_new_take(
128 &self,
129 expr: &Expression,
130 file: &FileModel,
131 load_contents_vars: &HashSet<&str>,
132 violations: &mut Vec<Violation>,
133 ) {
134 if let Expression::Call(call) = expr
135 && call.is_function("g_bytes_new_take")
136 && call.arguments.len() >= 2
137 {
138 if let Some(contents_var) = self.extract_contents_var(&call.arguments[0]) {
140 if load_contents_vars.contains(&contents_var) {
142 violations.push(self.violation_at(
143 &file.path,
144 &call.location,
145 "Consider using g_file_load_bytes/g_file_load_bytes_async instead of g_file_load_contents + g_bytes_new_take for simplicity".to_string(),
146 ));
147 }
148 }
149 }
150 }
151
152 fn extract_pointer_var<'a>(&self, arg: &'a Expression) -> Option<&'a str> {
154 if let Expression::Unary(unary) = arg
156 && unary.operator == UnaryOp::AddressOf
157 {
158 return unary.operand.extract_variable_name();
159 }
160
161 None
162 }
163
164 fn extract_contents_var<'a>(&self, arg: &'a Expression) -> Option<&'a str> {
167 match arg {
168 Expression::Identifier(id) => Some(id.name.as_str()),
170 Expression::FieldAccess(f) => f.location.as_str(),
171 Expression::Call(call) => {
173 if call.is_function("g_steal_pointer") && !call.arguments.is_empty() {
174 self.extract_pointer_var(&call.arguments[0])
175 } else {
176 None
177 }
178 }
179 _ => None,
180 }
181 }
182}