Skip to main content

gobject_linter/rules/
use_g_source_constants.rs

1use 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}