1use std::sync::LazyLock;
2
3use gobject_ast::model::{
4 FileModel, Parameter, StructField, TopLevelItem, TypeDefItem, TypeInfo, TypedefTarget,
5};
6
7use crate::{
8 ast_context::AstContext,
9 config::Config,
10 rules::{ConfigOption, Fix, Rule, Violation},
11};
12
13pub struct TypeStyle;
14
15impl Rule for TypeStyle {
16 fn name(&self) -> &'static str {
17 "type_style"
18 }
19
20 fn description(&self) -> &'static str {
21 "Enforce consistent use of GLib or C standard type aliases"
22 }
23
24 fn category(&self) -> crate::rules::Category {
25 crate::rules::Category::Style
26 }
27
28 fn fixable(&self) -> bool {
29 true
30 }
31
32 fn config_options(&self) -> &'static [ConfigOption] {
33 static OPTIONS: LazyLock<Vec<ConfigOption>> = LazyLock::new(|| {
34 vec![ConfigOption {
35 name: "style",
36 option_type: "string",
37 default_value: "\"glib\"",
38 example_value: "\"c\"",
39 description: "Type style to enforce: \"glib\" (prefer gint, gchar, …) or \"c\" (prefer int, char, int32_t, …)",
40 }]
41 });
42
43 &OPTIONS
44 }
45
46 fn check_all(
47 &self,
48 ast_context: &AstContext,
49 config: &Config,
50 violations: &mut Vec<Violation>,
51 ) {
52 let style = config
53 .get_rule_config(self.name())
54 .and_then(|rc| rc.options.get("style"))
55 .and_then(|v| v.as_str())
56 .unwrap_or("glib");
57
58 for (_, file) in ast_context.iter_all_files() {
59 for item in file.iter_all_items() {
60 self.check_item(item, file, style, violations);
61 }
62 }
63 }
64}
65
66impl TypeStyle {
67 fn check_item(
68 &self,
69 item: &TopLevelItem,
70 file: &FileModel,
71 style: &str,
72 violations: &mut Vec<Violation>,
73 ) {
74 match item {
75 TopLevelItem::FunctionDeclaration(decl) => {
76 self.check_type(&decl.return_type, file, style, violations);
77 self.check_params(&decl.parameters, file, style, violations);
78 }
79 TopLevelItem::FunctionDefinition(def) => {
80 self.check_type(&def.return_type, file, style, violations);
81 self.check_params(&def.parameters, file, style, violations);
82 for var in def.iter_local_declarations() {
83 self.check_type(&var.type_info, file, style, violations);
84 }
85 }
86 TopLevelItem::TypeDefinition(typedef_item) => {
87 self.check_typedef(typedef_item, file, style, violations);
88 }
89 TopLevelItem::Declaration(decl) => {
90 self.check_type(&decl.type_info, file, style, violations);
91 }
92 _ => {}
93 }
94 }
95
96 fn check_typedef(
97 &self,
98 item: &TypeDefItem,
99 file: &FileModel,
100 style: &str,
101 violations: &mut Vec<Violation>,
102 ) {
103 match item {
104 TypeDefItem::Typedef {
105 target,
106 struct_fields,
107 ..
108 } => {
109 match target {
110 TypedefTarget::Type(type_info) => {
111 self.check_type(type_info, file, style, violations);
112 }
113 TypedefTarget::Callback {
114 return_type,
115 parameters,
116 } => {
117 self.check_type(return_type, file, style, violations);
118 self.check_params(parameters, file, style, violations);
119 }
120 }
121 self.check_fields(struct_fields, file, style, violations);
122 }
123 TypeDefItem::Struct { fields, .. } => {
124 self.check_fields(fields, file, style, violations);
125 }
126 TypeDefItem::Enum(_) => {}
127 }
128 }
129
130 fn check_fields(
131 &self,
132 fields: &[StructField],
133 file: &FileModel,
134 style: &str,
135 violations: &mut Vec<Violation>,
136 ) {
137 for field in fields {
138 field.walk(&mut |f| {
139 self.check_type(&f.field_type, file, style, violations);
140 });
141 }
142 }
143
144 fn check_params(
145 &self,
146 parameters: &[Parameter],
147 file: &FileModel,
148 style: &str,
149 violations: &mut Vec<Violation>,
150 ) {
151 for param in parameters {
152 if let Parameter::Regular { type_info, .. } = param {
153 self.check_type(type_info, file, style, violations);
154 }
155 }
156 }
157
158 fn check_type(
159 &self,
160 type_info: &TypeInfo,
161 file: &FileModel,
162 style: &str,
163 violations: &mut Vec<Violation>,
164 ) {
165 let Some(basic) = type_info.as_basic() else {
166 return;
167 };
168
169 let canonical = if style == "c" {
170 basic.canonical_c(&type_info.base_type)
171 } else {
172 basic.canonical_glib()
173 };
174
175 let Some(canonical) = canonical else { return };
176
177 if type_info.base_type == canonical {
178 return;
179 }
180
181 let loc = &type_info.location;
182 let source_text = loc.as_str().unwrap_or("");
183 let new_text = source_text.replacen(&type_info.base_type, canonical, 1);
184 let fix = Fix::new(loc.start_byte, loc.end_byte, new_text);
185
186 violations.push(self.violation_with_fix_at(
187 &file.path,
188 &type_info.location,
189 format!("use `{}` instead of `{}`", canonical, type_info.base_type),
190 fix,
191 ));
192 }
193}