gobject_linter/rules/
signal_canonical_name.rs1use gobject_ast::model::{Expression, FileModel, FunctionDefItem};
2
3use crate::{
4 ast_context::AstContext,
5 config::Config,
6 rules::{Fix, Rule, Violation},
7};
8
9pub struct SignalCanonicalName;
10
11impl Rule for SignalCanonicalName {
12 fn name(&self) -> &'static str {
13 "signal_canonical_name"
14 }
15
16 fn description(&self) -> &'static str {
17 "Signal names should use hyphens (-) instead of underscores (_)"
18 }
19
20 fn category(&self) -> crate::rules::Category {
21 crate::rules::Category::Style
22 }
23
24 fn fixable(&self) -> bool {
25 true
26 }
27
28 fn check_func_impl(
29 &self,
30 _ast_context: &AstContext,
31 _config: &Config,
32 func: &FunctionDefItem,
33 file: &FileModel,
34 violations: &mut Vec<Violation>,
35 ) {
36 const NAME_ARG_FIRST: &[&str] = &[
37 "g_signal_new",
38 "g_signal_newv",
39 "g_signal_new_valist",
40 "g_signal_new_class_handler",
41 "g_signal_lookup",
42 ];
43 const NAME_ARG_SECOND: &[&str] = &[
44 "g_signal_connect",
45 "g_signal_connect_after",
46 "g_signal_connect_swapped",
47 "g_signal_connect_data",
48 "g_signal_connect_object",
49 "g_signal_emit_by_name",
50 "g_signal_stop_emission_by_name",
51 "g_signal_group_connect",
52 "g_signal_group_connect_after",
53 "g_signal_group_connect_swapped",
54 "g_signal_group_connect_object",
55 ];
56
57 for call in func.find_calls_matching(|name| {
58 NAME_ARG_FIRST.contains(&name) || NAME_ARG_SECOND.contains(&name)
59 }) {
60 let name = call.function_name_str().unwrap();
61 let arg_index = if NAME_ARG_FIRST.contains(&name) { 0 } else { 1 };
62 if let Some(arg_expr) = call.arguments.get(arg_index) {
63 self.check_signal_name_arg(arg_expr, file, violations);
64 }
65 }
66 }
67}
68
69impl SignalCanonicalName {
70 fn check_signal_name_arg(
71 &self,
72 expr: &Expression,
73 file: &FileModel,
74 violations: &mut Vec<Violation>,
75 ) {
76 if let Expression::StringLiteral(string_lit) = expr {
77 let raw = &string_lit.value;
78
79 let Some(first_close) = raw[1..].find('"') else {
81 return;
82 };
83 let first_str_content = &raw[1..1 + first_close];
84
85 let signal_name = first_str_content
86 .split("::")
87 .next()
88 .unwrap_or(first_str_content);
89
90 if signal_name.contains('_') {
91 let fixed_signal = signal_name.replace('_', "-");
92
93 let fixed_first_str = format!(
94 "\"{}{}\"",
95 fixed_signal,
96 &first_str_content[signal_name.len()..],
97 );
98
99 let fix_start = string_lit.location.start_byte;
100 let fix_end = fix_start + 1 + first_close + 1;
101
102 let fix = Fix::new(fix_start, fix_end, fixed_first_str);
103
104 violations.push(self.violation_with_fix_at(
105 &file.path,
106 &string_lit.location,
107 format!(
108 "Signal name '{}' should use hyphens instead of underscores: '{}'",
109 signal_name, fixed_signal
110 ),
111 fix,
112 ));
113 }
114 }
115 }
116}