use syn::{Token, parse::ParseStream};
pub trait TokenAnalyzer {
fn analyze(&self, input: ParseStream) -> bool;
#[allow(unused)]
fn description(&self) -> &'static str;
}
pub struct CommentAnalyzer;
impl TokenAnalyzer for CommentAnalyzer {
fn analyze(&self, input: ParseStream) -> bool {
self.contains_jsx_comment(&input)
}
fn description(&self) -> &'static str {
"Detects JSX-style comments in token streams"
}
}
impl CommentAnalyzer {
pub fn new() -> Self {
Self
}
pub fn contains_jsx_comment(&self, input: &ParseStream) -> bool {
if !input.peek(syn::token::Brace) {
return false;
}
let fork = input.fork();
if let Ok(group) = fork.parse::<proc_macro2::Group>()
&& group.delimiter() == proc_macro2::Delimiter::Brace
{
let stream = group.stream();
let tokens: Vec<proc_macro2::TokenTree> = stream.into_iter().collect();
if tokens.is_empty() {
return true;
}
if tokens.len() >= 4
&& let (
Some(proc_macro2::TokenTree::Punct(first)),
Some(proc_macro2::TokenTree::Punct(second)),
Some(proc_macro2::TokenTree::Literal(_)),
Some(proc_macro2::TokenTree::Punct(third)),
Some(proc_macro2::TokenTree::Punct(fourth)),
) = (
tokens.first(),
tokens.get(1),
tokens.get(2),
tokens.get(3),
tokens.get(4),
)
{
return first.as_char() == '/'
&& second.as_char() == '*'
&& third.as_char() == '*'
&& fourth.as_char() == '/';
}
}
false
}
pub fn extract_comment_content(&self, input: ParseStream) -> syn::Result<String> {
if !input.peek(syn::token::Brace) {
return Err(syn::Error::new(
input.span(),
"Expected brace group for comment",
));
}
let group: proc_macro2::Group = input.parse()?;
let stream = group.stream();
let tokens: Vec<proc_macro2::TokenTree> = stream.into_iter().collect();
if tokens.is_empty() {
return Ok("JSX comment".to_string());
}
if tokens.len() >= 5
&& let Some(proc_macro2::TokenTree::Literal(lit)) = tokens.get(2)
{
let content = lit.to_string().trim_matches('"').to_string();
return Ok(content);
}
Ok("JSX comment".to_string())
}
}
pub struct LogicalAndAnalyzer;
impl TokenAnalyzer for LogicalAndAnalyzer {
fn analyze(&self, input: ParseStream) -> bool {
self.contains_logical_and(&input)
}
fn description(&self) -> &'static str {
"Detects logical AND (&&) operators in token streams"
}
}
impl LogicalAndAnalyzer {
pub fn new() -> Self {
Self
}
pub fn contains_logical_and(&self, input: &ParseStream) -> bool {
let fork = input.fork();
let tokens: Result<Vec<proc_macro2::TokenTree>, syn::Error> = fork
.parse::<proc_macro2::TokenStream>()
.map(|stream| stream.into_iter().collect());
if let Ok(tokens) = tokens {
for (i, token) in tokens.iter().enumerate() {
if let proc_macro2::TokenTree::Punct(punct) = token
&& punct.as_char() == '&'
&& i + 1 < tokens.len()
&& let proc_macro2::TokenTree::Punct(next_punct) = &tokens[i + 1]
&& next_punct.as_char() == '&'
{
return true;
}
}
}
false
}
pub fn find_and_position(&self, tokens: &[proc_macro2::TokenTree]) -> Option<usize> {
for (i, token) in tokens.iter().enumerate() {
if let proc_macro2::TokenTree::Punct(punct) = token
&& punct.as_char() == '&'
&& i + 1 < tokens.len()
&& let proc_macro2::TokenTree::Punct(next_punct) = &tokens[i + 1]
&& next_punct.as_char() == '&'
{
return Some(i);
}
}
None
}
}
#[allow(unused)]
pub struct MatchAnalyzer;
impl TokenAnalyzer for MatchAnalyzer {
fn analyze(&self, input: ParseStream) -> bool {
self.contains_match_expression(&input)
}
fn description(&self) -> &'static str {
"Detects match expressions in token streams"
}
}
impl MatchAnalyzer {
#[allow(unused)]
pub fn contains_match_expression(&self, input: &ParseStream) -> bool {
let fork = input.fork();
let tokens: Result<Vec<proc_macro2::TokenTree>, syn::Error> = fork
.parse::<proc_macro2::TokenStream>()
.map(|stream| stream.into_iter().collect());
if let Ok(tokens) = tokens {
for token in tokens.iter() {
if let proc_macro2::TokenTree::Ident(ident) = token
&& *ident == "match"
{
return true;
}
}
}
false
}
}
pub struct ForLoopAnalyzer;
impl TokenAnalyzer for ForLoopAnalyzer {
fn analyze(&self, input: ParseStream) -> bool {
self.contains_for_loop(&input)
}
fn description(&self) -> &'static str {
"Detects for-loop expressions in token streams"
}
}
impl ForLoopAnalyzer {
pub fn new() -> Self {
Self
}
pub fn contains_for_loop(&self, input: &ParseStream) -> bool {
input.peek(Token![for])
}
}
pub struct ConditionalAnalyzer;
impl TokenAnalyzer for ConditionalAnalyzer {
fn analyze(&self, input: ParseStream) -> bool {
self.contains_conditional(&input)
}
fn description(&self) -> &'static str {
"Detects conditional expressions (if, if let, match, &&) in token streams"
}
}
impl ConditionalAnalyzer {
pub fn new() -> Self {
Self
}
pub fn contains_conditional(&self, input: &ParseStream) -> bool {
input.peek(Token![if])
|| input.peek(Token![match])
|| LogicalAndAnalyzer::new().contains_logical_and(input)
}
pub fn contains_if_let(&self, input: ParseStream) -> bool {
if !input.peek(Token![if]) {
return false;
}
let fork = input.fork();
let tokens: Result<Vec<proc_macro2::TokenTree>, syn::Error> = fork
.parse::<proc_macro2::TokenStream>()
.map(|stream| stream.into_iter().collect());
if let Ok(tokens) = tokens {
for (i, token) in tokens.iter().enumerate() {
if let proc_macro2::TokenTree::Ident(ident) = token
&& *ident == "if"
&& i + 1 < tokens.len()
&& let proc_macro2::TokenTree::Ident(next_ident) = &tokens[i + 1]
&& *next_ident == "let"
{
return true;
}
}
}
false
}
}
pub struct TokenStreamUtils;
impl TokenStreamUtils {
pub fn split_at_position(
tokens: &[proc_macro2::TokenTree],
position: usize,
skip_count: usize,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let before: proc_macro2::TokenStream = tokens[..position].iter().cloned().collect();
let after: proc_macro2::TokenStream =
tokens[position + skip_count..].iter().cloned().collect();
(before, after)
}
pub fn parse_stream_to_tokens(input: ParseStream) -> syn::Result<Vec<proc_macro2::TokenTree>> {
let tokens: proc_macro2::TokenStream = input.parse()?;
Ok(tokens.into_iter().collect())
}
}