use gobject_ast::model::{CallExpression, FileModel, FunctionDefItem};
use crate::{
ast_context::AstContext,
config::Config,
rules::{Fix, Rule, Violation},
};
pub struct UseExplicitDefaultFlags;
const FLAG_REPLACEMENTS: &[(&str, usize, &str)] = &[
("g_application_new", 1, "G_APPLICATION_DEFAULT_FLAGS"),
("gtk_widget_class_add_binding", 2, "GDK_NO_MODIFIER_MASK"),
(
"gtk_widget_class_add_binding_signal",
2,
"GDK_NO_MODIFIER_MASK",
),
(
"gtk_widget_class_add_binding_action",
2,
"GDK_NO_MODIFIER_MASK",
),
("gtk_shortcut_new", 1, "GDK_NO_MODIFIER_MASK"),
("g_dbus_connection_new", 1, "G_DBUS_CONNECTION_FLAGS_NONE"),
(
"g_dbus_connection_new_for_address",
1,
"G_DBUS_CONNECTION_FLAGS_NONE",
),
("g_dbus_proxy_new", 2, "G_DBUS_PROXY_FLAGS_NONE"),
("g_dbus_proxy_new_for_bus", 2, "G_DBUS_PROXY_FLAGS_NONE"),
("g_file_query_info", 2, "G_FILE_QUERY_INFO_NONE"),
("g_file_query_info_async", 2, "G_FILE_QUERY_INFO_NONE"),
("g_file_enumerate_children", 1, "G_FILE_QUERY_INFO_NONE"),
(
"g_file_enumerate_children_async",
1,
"G_FILE_QUERY_INFO_NONE",
),
("g_subprocess_new", 0, "G_SUBPROCESS_FLAGS_NONE"),
("g_subprocess_launcher_new", 0, "G_SUBPROCESS_FLAGS_NONE"),
(
"g_settings_new_with_backend_and_path",
3,
"G_SETTINGS_BIND_DEFAULT",
),
("gtk_application_new", 1, "G_APPLICATION_DEFAULT_FLAGS"),
("adw_application_new", 1, "G_APPLICATION_DEFAULT_FLAGS"),
("gtk_icon_theme_lookup_icon", 6, "GTK_ICON_LOOKUP_NONE"),
("gtk_icon_theme_lookup_by_gicon", 5, "GTK_ICON_LOOKUP_NONE"),
("gtk_drop_target_async_new", 1, "GDK_ACTION_NONE"),
("gtk_drop_target_new", 1, "GDK_ACTION_NONE"),
];
impl Rule for UseExplicitDefaultFlags {
fn name(&self) -> &'static str {
"use_explicit_default_flags"
}
fn description(&self) -> &'static str {
"Use explicit default flag constants (e.g., G_APPLICATION_DEFAULT_FLAGS) instead of 0"
}
fn category(&self) -> crate::rules::Category {
crate::rules::Category::Style
}
fn fixable(&self) -> bool {
true
}
fn check_func_impl(
&self,
_ast_context: &AstContext,
_config: &Config,
func: &FunctionDefItem,
file: &FileModel,
violations: &mut Vec<Violation>,
) {
let function_names: Vec<&str> = FLAG_REPLACEMENTS.iter().map(|(name, ..)| *name).collect();
for call in func.find_calls(&function_names) {
self.check_call(file, call, violations);
}
}
}
impl UseExplicitDefaultFlags {
fn check_call(&self, file: &FileModel, call: &CallExpression, violations: &mut Vec<Violation>) {
for &(target_func, arg_pos, replacement_const) in FLAG_REPLACEMENTS {
if call.is_function(target_func) {
if let Some(arg_expr) = call.get_arg(arg_pos)
&& arg_expr.is_zero()
{
let fix = Fix::new(
arg_expr.location().start_byte,
arg_expr.location().end_byte,
replacement_const.to_string(),
);
violations.push(self.violation_with_fix_at(
&file.path,
&call.location,
format!(
"Use {} instead of 0 for {}() flags parameter",
replacement_const, target_func
),
fix,
));
}
break;
}
}
}
}