gobject_linter/rules/
missing_g_begin_decls.rs1use gobject_ast::model::{Expression, PreprocessorDirective, TopLevelItem};
2
3use crate::{
4 ast_context::AstContext,
5 config::Config,
6 rules::{Category, Rule, Violation},
7};
8
9pub struct MissingGBeginDecls;
10
11impl Rule for MissingGBeginDecls {
12 fn name(&self) -> &'static str {
13 "missing_g_begin_decls"
14 }
15
16 fn description(&self) -> &'static str {
17 "Detect headers with missing or mismatched G_BEGIN_DECLS/G_END_DECLS"
18 }
19
20 fn category(&self) -> Category {
21 Category::Correctness
22 }
23
24 fn check_all(
25 &self,
26 ast_context: &AstContext,
27 _config: &Config,
28 violations: &mut Vec<Violation>,
29 ) {
30 for (path, file) in ast_context.iter_header_files() {
31 let decls_block = file.iter_all_items().find_map(|item| match item {
32 TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
33 location,
34 ..
35 }) => Some(location),
36 _ => None,
37 });
38
39 if let Some(loc) = &decls_block {
40 let source = loc.source();
42 let mut pos = loc.end_byte;
43 while pos > 0 && source[pos - 1] != b'\n' {
44 pos -= 1;
45 }
46 let end_line = source[pos..loc.end_byte].trim_ascii_start();
47 if !end_line.starts_with(b"G_END_DECLS") {
48 violations.push(self.violation_at(
49 path,
50 loc,
51 "G_BEGIN_DECLS without matching G_END_DECLS".to_string(),
52 ));
53 }
54 continue;
55 }
56
57 let orphan_begin = file.iter_all_items().find_map(|item| match item {
58 TopLevelItem::Preprocessor(PreprocessorDirective::Call {
59 directive,
60 location,
61 }) if directive == "G_BEGIN_DECLS" => Some(location),
62 TopLevelItem::Expression(expr)
63 if matches!(expr.as_ref(), Expression::Identifier(id) if id.name == "G_BEGIN_DECLS") =>
64 {
65 match expr.as_ref() {
66 Expression::Identifier(id) => Some(&id.location),
67 _ => None,
68 }
69 }
70 _ => None,
71 });
72
73 let orphan_end = file.iter_all_items().find_map(|item| match item {
74 TopLevelItem::Preprocessor(PreprocessorDirective::Call {
75 directive,
76 location,
77 }) if directive == "G_END_DECLS" => Some(location),
78 TopLevelItem::Expression(expr)
79 if matches!(expr.as_ref(), Expression::Identifier(id) if id.name == "G_END_DECLS") =>
80 {
81 match expr.as_ref() {
82 Expression::Identifier(id) => Some(&id.location),
83 _ => None,
84 }
85 }
86 _ => None,
87 });
88
89 if let Some(loc) = orphan_begin {
90 violations.push(self.violation_at(
91 path,
92 loc,
93 "G_BEGIN_DECLS without matching G_END_DECLS".to_string(),
94 ));
95 }
96
97 if let Some(loc) = orphan_end {
98 violations.push(self.violation_at(
99 path,
100 loc,
101 "G_END_DECLS without matching G_BEGIN_DECLS".to_string(),
102 ));
103 }
104
105 if orphan_begin.is_none()
106 && orphan_end.is_none()
107 && ast_context.is_public_header(path) == Some(true)
108 && file.has_declarations()
109 {
110 violations.push(self.violation(
111 path,
112 1,
113 1,
114 "Public header is missing G_BEGIN_DECLS/G_END_DECLS".to_string(),
115 ));
116 }
117 }
118 }
119}