gobject_linter/rules/
use_pragma_once.rs1use gobject_ast::model::{
2 ConditionalKind, PragmaKind, PreprocessorDirective, SourceLocation, TopLevelItem,
3};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Fix, Rule, Violation},
9};
10
11pub struct UsePragmaOnce;
12
13impl Rule for UsePragmaOnce {
14 fn name(&self) -> &'static str {
15 "use_pragma_once"
16 }
17
18 fn description(&self) -> &'static str {
19 "Suggest #pragma once instead of traditional include guards"
20 }
21
22 fn category(&self) -> crate::rules::Category {
23 crate::rules::Category::Style
24 }
25
26 fn fixable(&self) -> bool {
27 true
28 }
29
30 fn check_all(
31 &self,
32 ast_context: &AstContext,
33 _config: &Config,
34 violations: &mut Vec<Violation>,
35 ) {
36 for (path, file) in ast_context.iter_header_files() {
37 let has_pragma_once = file.top_level_items.iter().any(|item| {
39 matches!(
40 item,
41 TopLevelItem::Preprocessor(PreprocessorDirective::Pragma {
42 kind: PragmaKind::Once,
43 ..
44 })
45 )
46 });
47
48 if has_pragma_once {
49 continue; }
51
52 if let Some((ifndef_loc, define_loc, endif_loc, guard_name)) =
54 self.find_include_guard(&file.top_level_items)
55 {
56 let mut fixes = Vec::new();
60
61 let ifndef_fix_loc =
63 ifndef_loc.with_byte_range(ifndef_loc.start_byte, ifndef_loc.start_byte);
64 fixes.push(Fix::delete_line_and_trailing_blank(&ifndef_fix_loc));
65
66 let (define_start, define_end) = define_loc.find_line_bounds();
68 fixes.push(Fix::new(define_start, define_end, "#pragma once\n"));
69
70 fixes.push(Fix::delete_line_and_leading_blank(&endif_loc));
72
73 violations.push(self.violation_with_fixes_at(
74 path,
75 &ifndef_loc,
76 format!("Use #pragma once instead of include guard '{}'", guard_name),
77 fixes,
78 ));
79 }
80 }
81 }
82}
83
84impl UsePragmaOnce {
85 fn find_include_guard<'a>(
88 &self,
89 items: &'a [TopLevelItem],
90 ) -> Option<(SourceLocation, SourceLocation, SourceLocation, &'a str)> {
91 items
93 .iter()
94 .find(|item| !matches!(item, TopLevelItem::Comment(_)))
95 .and_then(|item| match item {
96 TopLevelItem::Preprocessor(PreprocessorDirective::Conditional {
97 kind: ConditionalKind::Ifndef,
98 condition: Some(name),
99 body,
100 location,
101 }) => {
102 let define_loc = self.find_matching_define(body, name)?;
104
105 let endif_loc = location.with_byte_range(location.end_byte, location.end_byte);
106 let ifndef_loc = location.clone();
107
108 Some((ifndef_loc, define_loc, endif_loc, name.as_str()))
109 }
110 _ => None, })
112 }
113
114 fn find_matching_define(
118 &self,
119 body: &[TopLevelItem],
120 guard_name: &str,
121 ) -> Option<SourceLocation> {
122 let non_comment_items: Vec<_> = body
123 .iter()
124 .filter(|item| !matches!(item, TopLevelItem::Comment(_)))
125 .collect();
126
127 if non_comment_items.len() < 2 {
128 return None;
129 }
130
131 non_comment_items
132 .first()
133 .and_then(|item| match item {
134 TopLevelItem::Preprocessor(PreprocessorDirective::Define {
135 name,
136 value,
137 location,
138 }) => {
139 if name == guard_name && value.is_none() {
140 Some(location)
141 } else {
142 None
143 }
144 }
145 _ => None,
146 })
147 .cloned()
148 }
149}