use gobject_ast::model::{
Assignment, AssignmentOp, CallExpression, Expression, FileModel, FunctionDefItem, Statement,
};
use crate::{
ast_context::AstContext,
config::Config,
rules::{Fix, Rule, Violation},
};
pub struct UseGBytesUnrefToData;
impl Rule for UseGBytesUnrefToData {
fn name(&self) -> &'static str {
"use_g_bytes_unref_to_data"
}
fn description(&self) -> &'static str {
"Use g_bytes_unref_to_data() instead of g_bytes_get_data() + g_bytes_unref()"
}
fn category(&self) -> crate::rules::Category {
crate::rules::Category::Complexity
}
fn fixable(&self) -> bool {
true
}
fn min_glib_version(&self) -> Option<(u32, u32)> {
Some((2, 32))
}
fn check_func_impl(
&self,
_ast_context: &AstContext,
config: &Config,
func: &FunctionDefItem,
file: &FileModel,
violations: &mut Vec<Violation>,
) {
Statement::walk_pairs(&func.body_statements, &mut |stmt1, stmt2| {
self.try_bytes_pattern(stmt1, stmt2, file, config, violations);
});
}
}
impl UseGBytesUnrefToData {
fn try_bytes_pattern(
&self,
stmt1: &Statement,
stmt2: &Statement,
file: &FileModel,
config: &Config,
violations: &mut Vec<Violation>,
) {
let Some((dest, bytes_var, size_arg, assignment, _call1)) =
self.extract_bytes_get_data(stmt1)
else {
return;
};
if self.extract_bytes_unref(stmt2, bytes_var).is_none() {
return;
};
let call = config
.style
.format_call("g_bytes_unref_to_data", &[bytes_var, size_arg]);
let replacement = format!("{} = {};", dest, call);
let message = format!(
"Use g_bytes_unref_to_data({}, {}) instead of g_bytes_get_data() followed by g_bytes_unref()",
bytes_var, size_arg
);
let stmt1_end = stmt1.location().find_semicolon_end();
let fixes = vec![
Fix::new(stmt1.location().start_byte, stmt1_end, replacement),
Fix::delete_line(stmt2.location()),
];
violations.push(self.violation_with_fixes_at(
&file.path,
&assignment.location,
message,
fixes,
));
}
fn extract_bytes_get_data<'a>(
&self,
stmt: &'a Statement,
) -> Option<(
&'a str,
&'a str,
&'a str,
&'a Assignment,
&'a CallExpression,
)> {
let Statement::Expression(expr_stmt) = stmt else {
return None;
};
let Expression::Assignment(assignment) = expr_stmt.as_ref() else {
return None;
};
if assignment.operator != AssignmentOp::Assign {
return None;
}
let Expression::Call(call) = assignment.rhs.as_ref() else {
return None;
};
if !call.is_function("g_bytes_get_data") {
return None;
}
if call.arguments.len() != 2 {
return None;
}
let bytes_var = call.get_arg_text(0)?;
let size_arg = call.get_arg_text(1)?;
let dest_var = assignment.lhs_as_text();
if dest_var.is_empty() {
return None;
}
Some((dest_var, bytes_var, size_arg, assignment, call))
}
fn extract_bytes_unref(&self, stmt: &Statement, expected_var: &str) -> Option<usize> {
let Statement::Expression(expr_stmt) = stmt else {
return None;
};
let Expression::Call(call) = expr_stmt.as_ref() else {
return None;
};
if !call.is_function("g_bytes_unref") {
return None;
}
if call.arguments.len() != 1 {
return None;
}
let arg_text = call.get_arg_text(0)?;
if arg_text != expected_var {
return None;
}
Some(expr_stmt.location().find_semicolon_end())
}
}