gobject_linter/
ast_context.rs1use std::{
2 path::Path,
3 sync::atomic::{AtomicUsize, Ordering},
4};
5
6use anyhow::Result;
7use globset::GlobSet;
8use gobject_ast::{
9 Parser,
10 model::{FileModel, Project},
11};
12use ignore::WalkBuilder;
13use indicatif::ProgressBar;
14use rayon::prelude::*;
15
16use crate::{meson::MesonIntrospection, type_alias_map::TypeAliasMap};
17
18pub struct AstContext {
21 pub project: Project,
22 pub meson_introspection: Option<MesonIntrospection>,
25 type_aliases: TypeAliasMap,
26}
27
28impl AstContext {
29 pub fn build_with_ignore(
31 directory: &Path,
32 ignore_matcher: &GlobSet,
33 spinner: Option<&ProgressBar>,
34 meson_introspection: Option<MesonIntrospection>,
35 ) -> Result<Self> {
36 let files: Vec<_> = WalkBuilder::new(directory)
40 .hidden(false) .git_ignore(true) .git_global(true) .git_exclude(true) .require_git(false) .build()
46 .filter_map(std::result::Result::ok)
47 .filter(|e| {
48 e.path()
49 .extension()
50 .is_some_and(|ext| ext == "h" || ext == "c")
51 })
52 .filter(|e| {
53 let path = e.path();
54 let relative_path = path.strip_prefix(directory).unwrap_or(path);
55 !ignore_matcher.is_match(relative_path)
56 })
57 .collect();
58
59 let total_files = files.len();
60 let counter = AtomicUsize::new(0);
61
62 let chunks: Vec<Vec<_>> = files
64 .par_iter()
65 .fold(
66 || (Parser::new().unwrap(), Vec::new()),
67 |(mut parser, mut results), entry| {
68 let i = counter.fetch_add(1, Ordering::Relaxed);
69 if let Some(sp) = spinner {
70 sp.set_message(format!("Parsing files... {}/{}", i + 1, total_files));
71 }
72 if let Ok(item) = parser.parse_file_to_model(entry.path()) {
73 results.push(item);
74 }
75 (parser, results)
76 },
77 )
78 .map(|(_, results)| results)
79 .collect();
80
81 let mut project = Project::new();
82 project.files.reserve(total_files);
83 for chunk in chunks {
84 for (path, model) in chunk {
85 project.files.insert(path, model);
86 }
87 }
88
89 project.resolve_all_gobject_types();
90 let type_aliases = TypeAliasMap::build(&project);
91
92 Ok(Self {
93 project,
94 meson_introspection,
95 type_aliases,
96 })
97 }
98
99 pub fn update_file(&mut self, file_path: &Path) -> Result<()> {
101 let mut parser = Parser::new()?;
102
103 if let Ok((path, model)) = parser.parse_file_to_model(file_path) {
104 self.project.files.insert(path, model);
105 } else {
106 self.project.files.remove(file_path);
107 }
108
109 self.project.resolve_all_gobject_types();
110 self.type_aliases = TypeAliasMap::build(&self.project);
111
112 Ok(())
113 }
114
115 pub fn type_aliases(&self) -> &TypeAliasMap {
117 &self.type_aliases
118 }
119
120 pub fn iter_all_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
121 self.project.iter_all_files()
122 }
123
124 pub fn iter_c_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
125 self.project.iter_c_files()
126 }
127
128 pub fn iter_header_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
129 self.project.iter_header_files()
130 }
131
132 pub fn iter_private_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
133 self.project.files.iter().filter_map(|(path, file)| {
134 let p = path.as_path();
135 if path.extension().is_some_and(|ext| ext == "c") {
136 return Some((p, file));
137 }
138 if self.is_public_header(p) == Some(true) {
139 return None;
140 }
141 Some((p, file))
142 })
143 }
144
145 pub fn is_public_header(&self, path: &Path) -> Option<bool> {
146 let m = self.meson_introspection.as_ref()?;
147 let gir = m.get_introspected_headers();
148 let installed = m.get_installed_headers();
149 Some(gir.contains(path) || installed.contains(path))
150 }
151
152 pub fn is_gir_header(&self, path: &Path) -> Option<bool> {
153 let m = self.meson_introspection.as_ref()?;
154 let gir = m.get_introspected_headers();
155 Some(gir.contains(path))
156 }
157
158 pub fn has_public_private_info(&self) -> bool {
159 self.meson_introspection.is_some()
160 }
161
162 pub fn find_func_doc(&self, name: &str) -> Option<&gobject_ast::model::FunctionDoc> {
163 self.project.find_func_doc(name)
164 }
165
166 pub fn find_type_doc(&self, type_name: &str) -> Option<&gobject_ast::model::TypeDoc> {
167 self.project.find_type_doc(type_name)
168 }
169
170 pub fn find_gobject_type_by_gtype(
171 &self,
172 gtype: &gobject_ast::model::GType,
173 ) -> Option<&gobject_ast::model::GObjectType> {
174 self.project.find_gobject_type_by_gtype(gtype)
175 }
176}