csskit_source_finder/
lib.rs

1use std::collections::HashSet;
2use std::io;
3use std::path::PathBuf;
4use std::str::from_utf8;
5
6use glob::glob;
7use grep_matcher::{Captures, Matcher};
8use grep_regex::{RegexMatcher, RegexMatcherBuilder};
9use grep_searcher::{Searcher, SearcherBuilder, Sink, SinkError, SinkMatch};
10use quote::ToTokens;
11use syn::{Type, parse_str};
12
13pub struct NodeMatcher<'a> {
14	matcher: &'a RegexMatcher,
15	matches: &'a mut HashSet<String>,
16}
17
18impl Sink for NodeMatcher<'_> {
19	type Error = io::Error;
20
21	fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result<bool, io::Error> {
22		let mut captures = self.matcher.new_captures()?;
23		let line = match from_utf8(mat.bytes()) {
24			Ok(matched) => matched,
25			Err(err) => return Err(io::Error::error_message(err)),
26		};
27		self.matcher.captures_iter(mat.bytes(), &mut captures, |captures| -> bool {
28			dbg!(&line, &captures, captures.get(2).map(|r| &line[r]), captures.get(5).map(|r| &line[r]));
29			let capture = &line[captures.get(5).unwrap()];
30			if !capture.is_empty() {
31				if let Ok(ty) = parse_str::<Type>(capture) {
32					self.matches.insert(ty.to_token_stream().to_string());
33				}
34			} else {
35				dbg!(&line);
36				panic!("#[visit] or unknown");
37			}
38			true
39		})?;
40		Ok(true)
41	}
42}
43
44pub fn find_visitable_nodes(dir: &str, matches: &mut HashSet<String>, path_callback: impl Fn(&PathBuf)) {
45	let matcher = RegexMatcherBuilder::new()
46		.multi_line(true)
47		.dot_matches_new_line(true)
48		.ignore_whitespace(true)
49		.build(
50			r#"
51			^\s*\#\[
52			# munch `cfg_atr(...,` and optional `derive(...)`.
53			(?:cfg_attr\([^,]+,\s*(?:derive\([^\)]+\),\s*)?)?
54			# match the #[visit] attribute
55			(visit)
56			# munch the data between the attribute and the definition
57			.*?
58			(
59				# Is this a public definition?
60				pub\s*(?:struct|enum)\s*
61			)
62			# munch any comments/attributes between this and our name (for macros)
63			(:?\n?\s*(:?\/\/|\#)[^\n]*)*
64			# finally grab the word (plus any generics)
65			\s*(\w*(:?<[^>]+>)?)"#,
66		)
67		.unwrap();
68	let mut searcher = SearcherBuilder::new().line_number(false).multi_line(true).build();
69	let entries = glob(dir).unwrap();
70	for entry in entries.filter_map(|p| p.ok()) {
71		path_callback(&entry);
72		let context = NodeMatcher { matcher: &matcher, matches };
73		searcher.search_path(&matcher, entry, context).unwrap();
74	}
75}
76
77#[test]
78fn test_find_visitable_nodes() {
79	use itertools::Itertools;
80	let mut matches = HashSet::new();
81	find_visitable_nodes("../css_ast/src/**/*.rs", &mut matches, |_| {});
82	::insta::assert_ron_snapshot!("all_visitable_nodes", matches.iter().sorted().collect::<Vec<_>>());
83}