gobject_linter/rules/
g_param_spec_null_nick_blurb.rs1use gobject_ast::model::{CallExpression, FileModel, GObjectType, ParamFlag, Property};
2
3use crate::{
4 ast_context::AstContext,
5 config::Config,
6 rules::{Fix, Rule, Violation},
7};
8
9pub struct GParamSpecNullNickBlurb;
10
11impl Rule for GParamSpecNullNickBlurb {
12 fn name(&self) -> &'static str {
13 "g_param_spec_null_nick_blurb"
14 }
15
16 fn description(&self) -> &'static str {
17 "Ensure g_param_spec_* functions have NULL for nick and blurb parameters"
18 }
19
20 fn category(&self) -> crate::rules::Category {
21 crate::rules::Category::Pedantic
22 }
23
24 fn fixable(&self) -> bool {
25 true
26 }
27
28 fn check_gobject_type(
29 &self,
30 _ast_context: &AstContext,
31 config: &Config,
32 gobject_type: &GObjectType,
33 file: &FileModel,
34 violations: &mut Vec<Violation>,
35 ) {
36 let static_flags = config.get_string_list(self.name(), "static_flags");
37
38 for assignment in &gobject_type.properties {
39 let Some(call) = assignment.param_spec_call() else {
40 continue;
41 };
42 self.check_call(file, call, assignment.property(), &static_flags, violations);
43 }
44 }
45}
46
47impl GParamSpecNullNickBlurb {
48 fn check_call(
49 &self,
50 file: &FileModel,
51 call: &CallExpression,
52 property: &Property,
53 custom_static_flags: &[String],
54 violations: &mut Vec<Violation>,
55 ) {
56 if call.arguments.len() < 3 {
57 return;
58 }
59
60 let has_custom_static_flag = property.flags.iter().any(|flag| {
61 if let ParamFlag::Unknown(name) = flag {
62 custom_static_flags.contains(name)
63 } else {
64 false
65 }
66 });
67
68 if has_custom_static_flag {
69 return;
70 }
71
72 let Some(nick_expr) = call.get_arg(1) else {
73 return;
74 };
75 let Some(blurb_expr) = call.get_arg(2) else {
76 return;
77 };
78
79 let nick_is_null = nick_expr.is_null();
80 let blurb_is_null = blurb_expr.is_null();
81
82 let mut issues = Vec::new();
83 if !nick_is_null {
84 issues.push("nick (parameter 2)");
85 }
86 if !blurb_is_null {
87 issues.push("blurb (parameter 3)");
88 }
89
90 if issues.is_empty() {
91 return;
92 }
93
94 let string_fix = if !nick_is_null && !blurb_is_null {
95 Fix::new(
96 nick_expr.location().start_byte,
97 blurb_expr.location().end_byte,
98 "NULL, NULL",
99 )
100 } else if !nick_is_null {
101 Fix::new(
102 nick_expr.location().start_byte,
103 nick_expr.location().end_byte,
104 "NULL",
105 )
106 } else {
107 Fix::new(
108 blurb_expr.location().start_byte,
109 blurb_expr.location().end_byte,
110 "NULL",
111 )
112 };
113
114 let mut fixes = vec![string_fix];
115
116 if let Some(new_flags) = self.compute_new_flags(&property.flags) {
117 let flags_expr = call.arguments.last().unwrap();
118 fixes.push(Fix::new(
119 flags_expr.location().start_byte,
120 flags_expr.location().end_byte,
121 new_flags,
122 ));
123 }
124
125 violations.push(self.violation_with_fixes_at(
126 &file.path,
127 &call.location,
128 format!(
129 "{} should have NULL for {}",
130 call.function_name(),
131 issues.join(" and ")
132 ),
133 fixes,
134 ));
135 }
136
137 fn compute_new_flags(&self, current_flags: &[ParamFlag]) -> Option<String> {
138 let needs_removal = current_flags.iter().any(|f| {
139 matches!(
140 f,
141 ParamFlag::StaticNick | ParamFlag::StaticBlurb | ParamFlag::StaticStrings
142 )
143 });
144 let has_name = current_flags
145 .iter()
146 .any(|f| matches!(f, ParamFlag::StaticName));
147
148 if !needs_removal && has_name {
149 return None;
150 }
151
152 let mut new_flags: Vec<ParamFlag> = current_flags
153 .iter()
154 .filter(|f| {
155 !matches!(
156 f,
157 ParamFlag::StaticNick | ParamFlag::StaticBlurb | ParamFlag::StaticStrings
158 )
159 })
160 .cloned()
161 .collect();
162
163 if !new_flags.iter().any(|f| matches!(f, ParamFlag::StaticName)) {
164 new_flags.push(ParamFlag::StaticName);
165 }
166
167 Some(if new_flags.is_empty() {
168 "0".to_string()
169 } else {
170 new_flags
171 .iter()
172 .map(ParamFlag::as_str)
173 .collect::<Vec<_>>()
174 .join(" | ")
175 })
176 }
177}