Skip to main content

gobject_linter/rules/
g_param_spec_null_nick_blurb.rs

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