gobject_linter/rules/
missing_export_macro.rs1use gobject_ast::model::{FileModel, FunctionDeclItem, GObjectType};
2
3use crate::{
4 ast_context::AstContext,
5 config::Config,
6 rules::{Category, Rule, Violation},
7};
8
9pub struct MissingExportMacro;
10
11impl Rule for MissingExportMacro {
12 fn name(&self) -> &'static str {
13 "missing_export_macro"
14 }
15
16 fn description(&self) -> &'static str {
17 "Detect public API functions and types without export macros"
18 }
19
20 fn long_description(&self) -> Option<&'static str> {
21 Some(
22 "Detects functions and types in public headers that lack export macros.\n\
23 Public API should be marked with appropriate export macros (e.g., G_MODULE_EXPORT, \
24 CLUTTER_EXPORT, META_EXPORT) to ensure proper symbol visibility.\n\
25 Public API should be marked with appropriate export macros (e.g., G_MODULE_EXPORT, \
26 CLUTTER_EXPORT, META_EXPORT) to ensure proper symbol visibility.",
27 )
28 }
29
30 fn category(&self) -> Category {
31 Category::Correctness
32 }
33
34 fn requires_meson(&self) -> bool {
35 true
36 }
37
38 fn opt_in(&self) -> bool {
39 true
40 }
41
42 fn opt_in_reason(&self) -> Option<&'static str> {
43 Some(
44 "May produce false positives as the parser can misidentify export macros in some codebases",
45 )
46 }
47
48 fn check_func_decl(
49 &self,
50 ast_context: &AstContext,
51 _config: &Config,
52 func: &FunctionDeclItem,
53 file: &FileModel,
54 violations: &mut Vec<Violation>,
55 ) {
56 if !ast_context.is_public_header(&file.path).unwrap_or(false) {
57 return;
58 }
59
60 if func.is_static {
61 return;
62 }
63
64 if func.export_macros.is_empty() {
65 violations.push(self.violation_at(
66 &file.path,
67 &func.location,
68 format!(
69 "Public function '{}' in header is missing an export macro (e.g., G_MODULE_EXPORT, *_EXPORT)",
70 func.name
71 ),
72 ));
73 }
74 }
75
76 fn check_gobject_type(
77 &self,
78 ast_context: &AstContext,
79 _config: &Config,
80 gobject_type: &GObjectType,
81 file: &FileModel,
82 violations: &mut Vec<Violation>,
83 ) {
84 if !ast_context.is_public_header(&file.path).unwrap_or(false) {
85 return;
86 }
87
88 if !gobject_type.kind.is_declare() {
89 return;
90 }
91
92 if gobject_type.export_macros.is_empty() {
93 violations.push(self.violation_at(
94 &file.path,
95 &gobject_type.location,
96 format!(
97 "'{}' is missing an export macro (e.g., G_MODULE_EXPORT, *_EXPORT)",
98 gobject_type.type_name
99 ),
100 ));
101 }
102 }
103}