1use std::path::PathBuf;
2
3use gobject_ast::model::{
4 EnumInfo, FileModel, FunctionDeclItem, FunctionDefItem, GObjectType, SourceLocation,
5};
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 ast_context::AstContext,
10 config::{Config, RuleLevel},
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize)]
15pub enum Category {
16 Correctness,
18 Suspicious,
20 Style,
22 Complexity,
24 Perf,
26 Pedantic,
28 Restriction,
30 Portability,
32 Introspection,
34}
35
36impl Category {
37 pub fn as_str(&self) -> &'static str {
38 match self {
39 Self::Correctness => "correctness",
40 Self::Suspicious => "suspicious",
41 Self::Style => "style",
42 Self::Complexity => "complexity",
43 Self::Perf => "perf",
44 Self::Pedantic => "pedantic",
45 Self::Restriction => "restriction",
46 Self::Portability => "portability",
47 Self::Introspection => "introspection",
48 }
49 }
50}
51
52impl std::fmt::Display for Category {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Self::Correctness => write!(f, "Correctness"),
56 Self::Suspicious => write!(f, "Suspicious"),
57 Self::Style => write!(f, "Style"),
58 Self::Complexity => write!(f, "Complexity"),
59 Self::Perf => write!(f, "Performance"),
60 Self::Pedantic => write!(f, "Pedantic"),
61 Self::Restriction => write!(f, "Restriction"),
62 Self::Portability => write!(f, "Portability"),
63 Self::Introspection => write!(f, "Introspection"),
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct Fix {
71 pub start_byte: usize,
73 pub end_byte: usize,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub replacement: Option<String>,
78}
79
80impl Fix {
81 pub fn new(start_byte: usize, end_byte: usize, replacement: impl Into<String>) -> Self {
83 Self {
84 start_byte,
85 end_byte,
86 replacement: Some(replacement.into()),
87 }
88 }
89
90 pub fn delete(start_byte: usize, end_byte: usize) -> Self {
92 Self {
93 start_byte,
94 end_byte,
95 replacement: None,
96 }
97 }
98
99 pub fn delete_line(location: &SourceLocation) -> Self {
101 let (start, end) = location.find_line_range();
102 Self::delete(start, end)
103 }
104
105 pub fn delete_line_and_leading_blank(location: &SourceLocation) -> Self {
107 let (start, end) = location.find_line_bounds();
108 Self::delete(start, end)
109 }
110
111 pub fn delete_line_and_trailing_blank(location: &SourceLocation) -> Self {
113 let (start, end) = location.find_line_bounds_with_following_blank();
114 Self::delete(start, end)
115 }
116}
117
118pub struct FunctionRename {
120 pub from: &'static str,
122 pub to: Option<&'static str>,
124 pub message: &'static str,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ConfigOption {
131 pub name: &'static str,
133 pub option_type: &'static str,
135 pub default_value: &'static str,
137 pub example_value: &'static str,
139 pub description: &'static str,
141}
142
143pub mod dead_code;
144pub mod deprecated_add_private;
145pub mod g_error_init;
146pub mod g_error_leak;
147pub mod g_object_virtual_methods_chain_up;
148pub mod g_param_spec_null_nick_blurb;
149pub mod g_param_spec_static_strings;
150pub mod g_source_id_not_stored;
151pub mod g_task_source_tag;
152pub mod gi_missing_since;
153pub mod gi_not_bindings_friendly;
154pub mod include_order;
155pub mod inconsistent_function_signature;
156pub mod matching_declare_define;
157pub mod missing_autoptr_cleanup;
158pub mod missing_export_macro;
159pub mod missing_g_begin_decls;
160pub mod missing_implementation;
161pub mod no_g_auto_macros;
162pub mod property_canonical_name;
163pub mod property_enum_convention;
164pub mod property_enum_coverage;
165pub mod property_switch_exhaustiveness;
166pub mod signal_canonical_name;
167pub mod signal_enum_coverage;
168pub mod strcmp_explicit_comparison;
169pub mod type_style;
170pub mod unnecessary_null_check;
171pub mod untranslated_string;
172pub mod unused_vfunc;
173pub mod use_auto_cleanup;
174pub mod use_clear_functions;
175pub mod use_explicit_default_flags;
176pub mod use_g_ascii_functions;
177pub mod use_g_bytes_unref_to_data;
178pub mod use_g_file_load_bytes;
179pub mod use_g_gnuc_flag_enum;
180pub mod use_g_new;
181pub mod use_g_object_class_install_properties;
182pub mod use_g_object_new_with_properties;
183pub mod use_g_object_notify_by_pspec;
184pub mod use_g_set_object;
185pub mod use_g_set_str;
186pub mod use_g_settings_typed;
187pub mod use_g_source_constants;
188pub mod use_g_source_once;
189pub mod use_g_steal_pointer;
190pub mod use_g_str_has_prefix_suffix;
191pub mod use_g_strcmp0;
192pub mod use_g_string_free_and_steal;
193pub mod use_g_strlcpy;
194pub mod use_g_value_set_static_string;
195pub mod use_g_variant_new_typed;
196pub mod use_pragma_once;
197
198pub use dead_code::DeadCode;
199pub use deprecated_add_private::DeprecatedAddPrivate;
200pub use g_error_init::GErrorInit;
201pub use g_error_leak::GErrorLeak;
202pub use g_object_virtual_methods_chain_up::GObjectVirtualMethodsChainUp;
203pub use g_param_spec_null_nick_blurb::GParamSpecNullNickBlurb;
204pub use g_param_spec_static_strings::GParamSpecStaticStrings;
205pub use g_source_id_not_stored::GSourceIdNotStored;
206pub use g_task_source_tag::GTaskSourceTag;
207pub use gi_missing_since::GiMissingSince;
208pub use gi_not_bindings_friendly::GiNotBindingsFriendly;
209pub use include_order::IncludeOrder;
210pub use inconsistent_function_signature::InconsistentFunctionSignature;
211pub use matching_declare_define::MatchingDeclareDefine;
212pub use missing_autoptr_cleanup::MissingAutoptrCleanup;
213pub use missing_export_macro::MissingExportMacro;
214pub use missing_g_begin_decls::MissingGBeginDecls;
215pub use missing_implementation::MissingImplementation;
216pub use no_g_auto_macros::NoGAutoMacros;
217pub use property_canonical_name::PropertyCanonicalName;
218pub use property_enum_convention::PropertyEnumConvention;
219pub use property_enum_coverage::PropertyEnumCoverage;
220pub use property_switch_exhaustiveness::PropertySwitchExhaustiveness;
221pub use signal_canonical_name::SignalCanonicalName;
222pub use signal_enum_coverage::SignalEnumCoverage;
223pub use strcmp_explicit_comparison::StrcmpExplicitComparison;
224pub use type_style::TypeStyle;
225pub use unnecessary_null_check::UnnecessaryNullCheck;
226pub use untranslated_string::UntranslatedString;
227pub use unused_vfunc::UnusedVfunc;
228pub use use_auto_cleanup::UseAutoCleanup;
229pub use use_clear_functions::UseClearFunctions;
230pub use use_explicit_default_flags::UseExplicitDefaultFlags;
231pub use use_g_ascii_functions::UseGAsciiFunctions;
232pub use use_g_bytes_unref_to_data::UseGBytesUnrefToData;
233pub use use_g_file_load_bytes::UseGFileLoadBytes;
234pub use use_g_gnuc_flag_enum::UseGGnucFlagEnum;
235pub use use_g_new::UseGNew;
236pub use use_g_object_class_install_properties::UseGObjectClassInstallProperties;
237pub use use_g_object_new_with_properties::UseGObjectNewWithProperties;
238pub use use_g_object_notify_by_pspec::UseGObjectNotifyByPspec;
239pub use use_g_set_object::UseGSetObject;
240pub use use_g_set_str::UseGSetStr;
241pub use use_g_settings_typed::UseGSettingsTyped;
242pub use use_g_source_constants::UseGSourceConstants;
243pub use use_g_source_once::UseGSourceOnce;
244pub use use_g_steal_pointer::UseGStealPointer;
245pub use use_g_str_has_prefix_suffix::UseGStrHasPrefixSuffix;
246pub use use_g_strcmp0::UseGStrcmp0;
247pub use use_g_string_free_and_steal::UseGStringFreeAndSteal;
248pub use use_g_strlcpy::UseGStrlcpy;
249pub use use_g_value_set_static_string::UseGValueSetStaticString;
250pub use use_g_variant_new_typed::UseGVariantNewTyped;
251pub use use_pragma_once::UsePragmaOnce;
252
253#[derive(Debug, Clone, serde::Serialize)]
254pub struct Violation {
255 pub file: PathBuf,
256 pub line: usize,
257 pub column: usize,
258 pub message: String,
259 pub rule: &'static str,
260 pub category: Category,
261 pub level: RuleLevel,
262 pub snippet: Option<String>,
263 pub rule_index: usize,
266 pub fixes: Vec<Fix>,
268}
269
270pub trait Rule: Send + Sync {
272 fn name(&self) -> &'static str;
274
275 fn description(&self) -> &'static str;
277
278 fn long_description(&self) -> Option<&'static str> {
280 None
281 }
282
283 fn category(&self) -> Category;
285
286 fn fixable(&self) -> bool {
288 false
289 }
290
291 fn requires_meson(&self) -> bool {
294 false
295 }
296
297 fn config_options(&self) -> &'static [ConfigOption] {
299 &[]
300 }
301
302 fn min_glib_version(&self) -> Option<(u32, u32)> {
305 None
306 }
307
308 fn requires_auto_cleanup(&self) -> bool {
311 false
312 }
313
314 fn opt_in(&self) -> bool {
317 false
318 }
319
320 fn opt_in_reason(&self) -> Option<&'static str> {
322 None
323 }
324
325 #[allow(unused_variables)]
328 fn check_func_impl(
329 &self,
330 ast_context: &AstContext,
331 config: &Config,
332 func: &FunctionDefItem,
333 file: &FileModel,
334 violations: &mut Vec<Violation>,
335 ) {
336 }
338
339 #[allow(unused_variables)]
342 fn check_func_decl(
343 &self,
344 ast_context: &AstContext,
345 config: &Config,
346 func: &FunctionDeclItem,
347 file: &FileModel,
348 violations: &mut Vec<Violation>,
349 ) {
350 }
352
353 #[allow(unused_variables)]
355 fn check_enum(
356 &self,
357 ast_context: &AstContext,
358 config: &Config,
359 enum_info: &EnumInfo,
360 file: &FileModel,
361 violations: &mut Vec<Violation>,
362 ) {
363 }
365
366 #[allow(unused_variables)]
370 fn check_gobject_type(
371 &self,
372 ast_context: &AstContext,
373 config: &Config,
374 gobject_type: &GObjectType,
375 file: &FileModel,
376 violations: &mut Vec<Violation>,
377 ) {
378 }
380
381 fn check_all(
387 &self,
388 ast_context: &AstContext,
389 config: &Config,
390 violations: &mut Vec<Violation>,
391 ) {
392 for (path, file) in ast_context.iter_all_files() {
393 let ext = path.extension().and_then(|e| e.to_str());
394 if ext == Some("c") {
395 for func in file.iter_function_definitions() {
396 self.check_func_impl(ast_context, config, func, file, violations);
397 }
398 }
399 if ext == Some("h") {
400 for func in file.iter_function_declarations() {
401 self.check_func_decl(ast_context, config, func, file, violations);
402 }
403 }
404 for gt in file.iter_all_gobject_types() {
405 self.check_gobject_type(ast_context, config, gt, file, violations);
406 }
407 for enum_info in file.iter_all_enums() {
408 self.check_enum(ast_context, config, enum_info, file, violations);
409 }
410 }
411 }
412
413 fn check_function_renames(
416 &self,
417 func: &FunctionDefItem,
418 file: &FileModel,
419 config: &Config,
420 violations: &mut Vec<Violation>,
421 renames: &[FunctionRename],
422 ) {
423 let names: Vec<&str> = renames.iter().map(|r| r.from).collect();
424 for call in func.find_calls(&names) {
425 let Some(func_name) = call.function_name_str() else {
426 continue;
427 };
428 let Some(rename) = renames.iter().find(|r| r.from == func_name) else {
429 continue;
430 };
431
432 if let Some(new_name) = rename.to {
433 let args: Vec<&str> = call
434 .arguments
435 .iter()
436 .filter_map(|arg| arg.location().as_str())
437 .collect();
438 let replacement = config.style.format_call(new_name, &args);
439 let fix = Fix::new(
440 call.location.start_byte,
441 call.location.end_byte,
442 replacement,
443 );
444 violations.push(self.violation_with_fix_at(
445 &file.path,
446 &call.location,
447 rename.message.to_string(),
448 fix,
449 ));
450 } else {
451 violations.push(self.violation_at(
452 &file.path,
453 &call.location,
454 rename.message.to_string(),
455 ));
456 }
457 }
458 }
459
460 fn violation(
462 &self,
463 file: &std::path::Path,
464 line: usize,
465 column: usize,
466 message: String,
467 ) -> Violation {
468 self.violation_with_fixes(file, line, column, message, vec![])
469 }
470
471 fn violation_with_fix(
473 &self,
474 file: &std::path::Path,
475 line: usize,
476 column: usize,
477 message: String,
478 fix: Fix,
479 ) -> Violation {
480 self.violation_with_fixes(file, line, column, message, vec![fix])
481 }
482
483 fn violation_with_fixes(
485 &self,
486 file: &std::path::Path,
487 line: usize,
488 column: usize,
489 message: String,
490 fixes: Vec<Fix>,
491 ) -> Violation {
492 Violation {
493 file: file.to_path_buf(),
494 line,
495 column,
496 message,
497 rule: self.name(),
498 category: self.category(),
499 level: RuleLevel::Error, snippet: None,
501 rule_index: 0,
502 fixes,
503 }
504 }
505
506 fn violation_at(
507 &self,
508 file: &std::path::Path,
509 location: &SourceLocation,
510 message: String,
511 ) -> Violation {
512 self.violation(file, location.line, location.column, message)
513 }
514
515 fn violation_with_fix_at(
516 &self,
517 file: &std::path::Path,
518 location: &SourceLocation,
519 message: String,
520 fix: Fix,
521 ) -> Violation {
522 self.violation_with_fix(file, location.line, location.column, message, fix)
523 }
524
525 fn violation_with_fixes_at(
526 &self,
527 file: &std::path::Path,
528 location: &SourceLocation,
529 message: String,
530 fixes: Vec<Fix>,
531 ) -> Violation {
532 self.violation_with_fixes(file, location.line, location.column, message, fixes)
533 }
534}