csskit_source_finder/
lib.rs

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