use std::collections::HashSet;
use gobject_ast::{Argument, Expression, Statement, UnaryOp, top_level::FunctionDefItem};
use crate::{
ast_context::AstContext,
config::Config,
rules::{Rule, Violation},
};
pub struct UseGFileLoadBytes;
impl Rule for UseGFileLoadBytes {
fn name(&self) -> &'static str {
"use_g_file_load_bytes"
}
fn description(&self) -> &'static str {
"Suggest g_file_load_bytes/g_file_load_bytes_async instead of g_file_load_contents + g_bytes_new_take"
}
fn category(&self) -> crate::rules::Category {
crate::rules::Category::Complexity
}
fn fixable(&self) -> bool {
false }
fn check_func_impl(
&self,
_ast_context: &AstContext,
_config: &Config,
func: &FunctionDefItem,
file: &gobject_ast::FileModel,
violations: &mut Vec<Violation>,
) {
self.check_function(func, file, violations);
}
}
impl UseGFileLoadBytes {
fn check_function(
&self,
func: &FunctionDefItem,
file: &gobject_ast::FileModel,
violations: &mut Vec<Violation>,
) {
let load_contents_vars = self.find_load_contents_vars(func, &file.source);
self.find_bytes_new_take_violations(
&func.body_statements,
file,
&load_contents_vars,
violations,
);
}
fn find_load_contents_vars<'a>(
&self,
func: &FunctionDefItem,
source: &'a [u8],
) -> HashSet<&'a str> {
let mut result = HashSet::new();
for call in func.find_calls(&["g_file_load_contents", "g_file_load_contents_finish"]) {
if call.arguments.len() >= 6 {
if let Some(contents_var) = self.extract_pointer_var(&call.arguments[2], source) {
result.insert(contents_var);
}
}
}
result
}
fn find_bytes_new_take_violations(
&self,
statements: &[Statement],
file: &gobject_ast::FileModel,
load_contents_vars: &HashSet<&str>,
violations: &mut Vec<Violation>,
) {
for stmt in statements {
stmt.walk(&mut |s| match s {
Statement::Expression(expr_stmt) => {
self.check_expr_for_bytes_new_take(
expr_stmt,
file,
load_contents_vars,
violations,
);
}
Statement::Declaration(decl) => {
if let Some(init) = &decl.initializer {
self.check_expr_for_bytes_new_take(
init,
file,
load_contents_vars,
violations,
);
}
}
Statement::Return(ret) => {
if let Some(expr) = &ret.value {
self.check_expr_for_bytes_new_take(
expr,
file,
load_contents_vars,
violations,
);
}
}
_ => {}
});
}
}
fn check_expr_for_bytes_new_take(
&self,
expr: &Expression,
file: &gobject_ast::FileModel,
load_contents_vars: &HashSet<&str>,
violations: &mut Vec<Violation>,
) {
if let Expression::Call(call) = expr
&& call.is_function("g_bytes_new_take")
&& call.arguments.len() >= 2
{
if let Some(contents_var) = self.extract_contents_var(&call.arguments[0], &file.source)
{
if load_contents_vars.contains(&contents_var) {
violations.push(self.violation(
&file.path,
call.location.line,
call.location.column,
"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(),
));
}
}
}
}
fn extract_pointer_var<'a>(&self, arg: &Argument, source: &'a [u8]) -> Option<&'a str> {
let Argument::Expression(expr) = arg;
if let Expression::Unary(unary) = expr.as_ref()
&& unary.operator == UnaryOp::AddressOf
{
return unary.operand.extract_variable_name(source);
}
None
}
fn extract_contents_var<'a>(&self, arg: &'a Argument, source: &'a [u8]) -> Option<&'a str> {
let Argument::Expression(expr) = arg;
match expr.as_ref() {
Expression::Identifier(id) => Some(id.name.as_str()),
Expression::FieldAccess(f) => f.location.as_str(source),
Expression::Call(call) => {
if call.is_function("g_steal_pointer") && !call.arguments.is_empty() {
self.extract_pointer_var(&call.arguments[0], source)
} else {
None
}
}
_ => None,
}
}
}