bindgen/
regex_set.rs
1#![deny(clippy::missing_docs_in_private_items)]
3
4use regex::RegexSet as RxSet;
5use std::cell::Cell;
6
7#[derive(Clone, Debug, Default)]
9pub(crate) struct RegexSet {
10 items: Vec<Box<str>>,
11 matched: Vec<Cell<bool>>,
14 set: Option<RxSet>,
15 record_matches: bool,
17}
18
19impl RegexSet {
20 pub(crate) fn is_empty(&self) -> bool {
22 self.items.is_empty()
23 }
24
25 pub(crate) fn insert<S>(&mut self, string: S)
27 where
28 S: AsRef<str>,
29 {
30 self.items.push(string.as_ref().to_owned().into_boxed_str());
31 self.matched.push(Cell::new(false));
32 self.set = None;
33 }
34
35 pub(crate) fn get_items(&self) -> &[Box<str>] {
37 &self.items
38 }
39
40 pub(crate) fn unmatched_items(&self) -> impl Iterator<Item = &str> {
43 self.items.iter().enumerate().filter_map(move |(i, item)| {
44 if !self.record_matches || self.matched[i].get() {
45 return None;
46 }
47
48 Some(item.as_ref())
49 })
50 }
51
52 #[inline]
57 #[allow(unused)]
58 pub(crate) fn build(&mut self, record_matches: bool) {
59 self.build_inner(record_matches, None);
60 }
61
62 #[cfg(all(feature = "__cli", feature = "experimental"))]
63 #[inline]
69 pub(crate) fn build_with_diagnostics(
70 &mut self,
71 record_matches: bool,
72 name: Option<&'static str>,
73 ) {
74 self.build_inner(record_matches, name);
75 }
76
77 #[cfg(all(not(feature = "__cli"), feature = "experimental"))]
78 #[inline]
84 pub(crate) fn build_with_diagnostics(
85 &mut self,
86 record_matches: bool,
87 name: Option<&'static str>,
88 ) {
89 self.build_inner(record_matches, name);
90 }
91
92 fn build_inner(
93 &mut self,
94 record_matches: bool,
95 _name: Option<&'static str>,
96 ) {
97 let items = self.items.iter().map(|item| format!("^({item})$"));
98 self.record_matches = record_matches;
99 self.set = match RxSet::new(items) {
100 Ok(x) => Some(x),
101 Err(e) => {
102 warn!("Invalid regex in {:?}: {e:?}", self.items);
103 #[cfg(feature = "experimental")]
104 if let Some(name) = _name {
105 invalid_regex_warning(self, e, name);
106 }
107 None
108 }
109 }
110 }
111
112 pub(crate) fn matches<S>(&self, string: S) -> bool
114 where
115 S: AsRef<str>,
116 {
117 let s = string.as_ref();
118 let Some(ref set) = self.set else {
119 return false;
120 };
121
122 if !self.record_matches {
123 return set.is_match(s);
124 }
125
126 let matches = set.matches(s);
127 if !matches.matched_any() {
128 return false;
129 }
130 for i in &matches {
131 self.matched[i].set(true);
132 }
133
134 true
135 }
136}
137
138#[cfg(feature = "experimental")]
139fn invalid_regex_warning(
140 set: &RegexSet,
141 err: regex::Error,
142 name: &'static str,
143) {
144 use crate::diagnostics::{Diagnostic, Level, Slice};
145
146 let mut diagnostic = Diagnostic::default();
147
148 match err {
149 regex::Error::Syntax(string) => {
150 if string.starts_with("regex parse error:\n") {
151 let mut source = String::new();
152
153 let mut parsing_source = true;
154
155 for line in string.lines().skip(1) {
156 if parsing_source {
157 if line.starts_with(' ') {
158 source.push_str(line);
159 source.push('\n');
160 continue;
161 }
162 parsing_source = false;
163 }
164 let error = "error: ";
165 if line.starts_with(error) {
166 let (_, msg) = line.split_at(error.len());
167 diagnostic.add_annotation(msg.to_owned(), Level::Error);
168 } else {
169 diagnostic.add_annotation(line.to_owned(), Level::Info);
170 }
171 }
172 let mut slice = Slice::default();
173 slice.with_source(source);
174 diagnostic.add_slice(slice);
175
176 diagnostic.with_title(
177 "Error while parsing a regular expression.",
178 Level::Warning,
179 );
180 } else {
181 diagnostic.with_title(string, Level::Warning);
182 }
183 }
184 err => {
185 let err = err.to_string();
186 diagnostic.with_title(err, Level::Warning);
187 }
188 }
189
190 diagnostic.add_annotation(
191 format!("This regular expression was passed via `{name}`."),
192 Level::Note,
193 );
194
195 if set.items.iter().any(|item| item.as_ref() == "*") {
196 diagnostic.add_annotation("Wildcard patterns \"*\" are no longer considered valid. Use \".*\" instead.", Level::Help);
197 }
198 diagnostic.display();
199}