gobject_linter/rules/
use_g_source_constants.rs1use std::collections::HashSet;
2
3use gobject_ast::model::{Expression, Statement};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Fix, Rule, Violation},
9};
10
11fn gsource_callback_arg_index(func_name: &str) -> Option<usize> {
12 match func_name {
13 "g_idle_add" => Some(0),
14 "g_idle_add_full"
15 | "g_timeout_add"
16 | "g_timeout_add_seconds"
17 | "gtk_widget_add_tick_callback" => Some(1),
18 "g_timeout_add_full" | "g_timeout_add_seconds_full" => Some(2),
19 _ => None,
20 }
21}
22
23pub struct UseGSourceConstants;
24
25impl Rule for UseGSourceConstants {
26 fn name(&self) -> &'static str {
27 "use_g_source_constants"
28 }
29
30 fn description(&self) -> &'static str {
31 "Use G_SOURCE_CONTINUE/G_SOURCE_REMOVE instead of TRUE/FALSE in GSourceFunc callbacks"
32 }
33
34 fn category(&self) -> crate::rules::Category {
35 crate::rules::Category::Style
36 }
37
38 fn fixable(&self) -> bool {
39 true
40 }
41
42 fn check_all(
43 &self,
44 ast_context: &AstContext,
45 _config: &Config,
46 violations: &mut Vec<Violation>,
47 ) {
48 let mut callbacks: HashSet<&str> = HashSet::new();
49
50 for (_path, file) in ast_context.iter_c_files() {
51 for func in file.iter_function_definitions() {
52 for call in
53 func.find_calls_matching(|name| gsource_callback_arg_index(name).is_some())
54 {
55 if let Some(idx) = gsource_callback_arg_index(call.function_name_str().unwrap())
56 && let Some(name) =
57 call.get_arg(idx).and_then(|a| a.extract_identifier_name())
58 {
59 callbacks.insert(name);
60 }
61 }
62 }
63 }
64
65 if callbacks.is_empty() {
66 return;
67 }
68
69 for (path, file) in ast_context.iter_c_files() {
70 for func in file.iter_function_definitions() {
71 if callbacks.contains(func.name.as_str()) {
72 self.check_statements(path, &func.body_statements, violations);
73 }
74 }
75 }
76 }
77}
78
79impl UseGSourceConstants {
80 fn check_statements(
81 &self,
82 file_path: &std::path::Path,
83 statements: &[Statement],
84 violations: &mut Vec<Violation>,
85 ) {
86 for stmt in statements {
87 for ret_stmt in stmt.iter_returns() {
88 if let Some(value) = &ret_stmt.value {
89 self.check_return_value(file_path, value, violations);
90 }
91 }
92 }
93 }
94
95 fn check_return_value(
96 &self,
97 file_path: &std::path::Path,
98 expr: &Expression,
99 violations: &mut Vec<Violation>,
100 ) {
101 expr.walk(&mut |e| {
102 let (old_name, replacement) = if e.is_truthy() {
103 ("TRUE", "G_SOURCE_CONTINUE")
104 } else if e.is_falsy() {
105 ("FALSE", "G_SOURCE_REMOVE")
106 } else {
107 return;
108 };
109
110 let loc = e.location();
111 let message = format!(
112 "Use {} instead of {} in GSourceFunc callback",
113 replacement, old_name
114 );
115 let fix = Fix::new(loc.start_byte, loc.end_byte, replacement);
116
117 violations.push(self.violation_with_fix_at(file_path, loc, message, fix));
118 });
119 }
120}