use libgraphql_parser::smallvec::smallvec;
use libgraphql_parser::token::GraphQLToken;
use libgraphql_parser::token::GraphQLTokenKind;
use libgraphql_parser::token::GraphQLTriviaToken;
use libgraphql_parser::token::GraphQLTriviaTokenVec;
use libgraphql_parser::token::GraphQLTokenSource;
use libgraphql_parser::ByteSpan;
use libgraphql_parser::GraphQLErrorNote;
use libgraphql_parser::SourceMap;
use libgraphql_parser::SourcePosition;
use libgraphql_parser::SourceSpan;
use proc_macro2::Delimiter;
use proc_macro2::Group;
use proc_macro2::Ident;
use proc_macro2::Literal;
use proc_macro2::Punct;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::iter::Peekable;
use std::rc::Rc;
const DOT_ERROR_MSG: &str = "Unexpected `.`";
const DOUBLE_DOT_ERROR_MSG: &str =
"Unexpected `..` (use `...` for spread operator)";
const SPACED_DOT_DOT_ERROR_MSG: &str =
"Unexpected `. .` (use `...` for spread operator)";
const PENDING_MINUS_ERROR_MSG: &str = "Unexpected `-`";
pub(crate) struct RustMacroGraphQLTokenSource {
buffered_tokens: VecDeque<GraphQLToken<'static>>,
source_map: SourceMap<'static>,
}
impl RustMacroGraphQLTokenSource {
pub fn new(
input: TokenStream,
span_map: Rc<RefCell<HashMap<u32, Span>>>,
) -> Self {
let mut tokenizer = Tokenizer {
tokens: input.into_iter().peekable(),
pending: VecDeque::new(),
pending_trivia: smallvec![],
finished: false,
last_span: None,
next_synthetic_offset: 0,
span_map: Rc::clone(&span_map),
source_map_entries: Vec::new(),
};
let buffered_tokens: VecDeque<_> =
tokenizer.tokenize_all().into();
let source_map = SourceMap::new_precomputed(
tokenizer.source_map_entries, None,
);
Self {
buffered_tokens,
source_map,
}
}
}
impl Iterator for RustMacroGraphQLTokenSource {
type Item = GraphQLToken<'static>;
fn next(&mut self) -> Option<Self::Item> {
self.buffered_tokens.pop_front()
}
}
impl GraphQLTokenSource<'static> for RustMacroGraphQLTokenSource {
fn source_map(&self) -> &SourceMap<'static> {
&self.source_map
}
fn into_source_map(self) -> SourceMap<'static> {
self.source_map
}
}
struct PendingToken {
kind: GraphQLTokenKind<'static>,
preceding_trivia: GraphQLTriviaTokenVec<'static>,
span: Span,
ending_span: Option<Span>,
}
struct Tokenizer {
tokens: Peekable<proc_macro2::token_stream::IntoIter>,
pending: VecDeque<PendingToken>,
pending_trivia: GraphQLTriviaTokenVec<'static>,
finished: bool,
last_span: Option<Span>,
next_synthetic_offset: u32,
span_map: Rc<RefCell<HashMap<u32, Span>>>,
source_map_entries: Vec<(u32, SourcePosition)>,
}
impl Tokenizer {
fn tokenize_all(&mut self) -> Vec<GraphQLToken<'static>> {
let mut output = Vec::new();
while let Some(token) = self.produce_next_token() {
let is_eof =
matches!(token.kind, GraphQLTokenKind::Eof);
output.push(token);
if is_eof {
break;
}
}
output
}
fn produce_next_token(
&mut self,
) -> Option<GraphQLToken<'static>> {
if self.finished {
return None;
}
while self.pending.len() < 3
&& self.tokens.peek().is_some()
{
if let Some(tree) = self.tokens.next() {
self.process_token_tree(tree);
}
}
if let Some(block_string) =
self.try_combine_block_string()
{
self.last_span = block_string
.ending_span
.or(Some(block_string.span));
return Some(
self.pending_to_token(block_string),
);
}
if let Some(negative_number) =
self.try_combine_negative_number()
{
self.last_span = negative_number
.ending_span
.or(Some(negative_number.span));
return Some(
self.pending_to_token(negative_number),
);
}
if !self.pending.is_empty() {
let pending = self.pending.pop_front().unwrap();
self.last_span =
pending.ending_span.or(Some(pending.span));
return Some(self.pending_to_token(pending));
}
self.finished = true;
Some(self.make_eof_token())
}
fn pending_to_token(
&mut self,
pending: PendingToken,
) -> GraphQLToken<'static> {
let span =
self.make_pending_byte_span(&pending);
GraphQLToken {
kind: pending.kind,
preceding_trivia: pending.preceding_trivia,
span,
}
}
fn next_offset(&mut self) -> u32 {
let offset = self.next_synthetic_offset;
self.next_synthetic_offset += 1;
offset
}
fn span_start_position(
span: &Span,
synthetic_offset: u32,
) -> SourcePosition {
let lc = span.start();
SourcePosition::new(
lc.line.saturating_sub(1),
lc.column,
None,
synthetic_offset as usize,
)
}
fn span_end_position(
span: &Span,
synthetic_offset: u32,
) -> SourcePosition {
let lc = span.end();
SourcePosition::new(
lc.line.saturating_sub(1),
lc.column,
None,
synthetic_offset as usize,
)
}
fn make_source_span(&mut self, span: &Span) -> SourceSpan {
let byte_span = self.make_byte_span(span);
SourceSpan::new(
Self::span_start_position(span, byte_span.start),
Self::span_end_position(span, byte_span.end),
)
}
fn make_byte_span(&mut self, span: &Span) -> ByteSpan {
let start = self.next_offset();
let end = self.next_offset();
self.span_map.borrow_mut().insert(start, *span);
self.source_map_entries.push((
start,
Self::span_start_position(span, start),
));
self.source_map_entries.push((
end,
Self::span_end_position(span, end),
));
ByteSpan::new(start, end)
}
fn make_pending_byte_span(
&mut self,
pending: &PendingToken,
) -> ByteSpan {
let start = self.next_offset();
let end = self.next_offset();
self.span_map
.borrow_mut()
.insert(start, pending.span);
self.source_map_entries.push((
start,
Self::span_start_position(&pending.span, start),
));
let end_span =
pending.ending_span.as_ref().unwrap_or(&pending.span);
self.source_map_entries.push((
end,
Self::span_end_position(end_span, end),
));
ByteSpan::new(start, end)
}
fn make_pending_token(
&mut self,
kind: GraphQLTokenKind<'static>,
span: Span,
) -> PendingToken {
PendingToken {
kind,
preceding_trivia: std::mem::take(
&mut self.pending_trivia,
),
span,
ending_span: None,
}
}
fn is_raw_string(s: &str) -> bool {
s.starts_with("r\"") || s.starts_with("r#")
}
fn spans_are_adjacent(
first: &Span,
second: &Span,
) -> bool {
let first_end = first.end();
let second_start = second.start();
first_end.line == second_start.line
&& first_end.column == second_start.column
}
fn spans_on_same_line(
first: &Span,
second: &Span,
) -> bool {
first.start().line == second.start().line
}
fn process_token_tree(&mut self, tree: TokenTree) {
match tree {
TokenTree::Group(group) => {
self.process_group_token(group)
},
TokenTree::Ident(ident) => {
self.process_ident_token(ident)
},
TokenTree::Punct(punct) => {
self.process_punct_token(punct)
},
TokenTree::Literal(lit) => {
self.process_literal_token(lit)
},
}
}
fn process_group_token(&mut self, group: Group) {
let span = group.span();
match group.delimiter() {
Delimiter::Brace => {
let open = self.make_pending_token(
GraphQLTokenKind::CurlyBraceOpen,
span,
);
self.pending.push_back(open);
for inner in group.stream() {
self.process_token_tree(inner);
}
let close = self.make_pending_token(
GraphQLTokenKind::CurlyBraceClose,
span,
);
self.pending.push_back(close);
},
Delimiter::Bracket => {
let open = self.make_pending_token(
GraphQLTokenKind::SquareBracketOpen,
span,
);
self.pending.push_back(open);
for inner in group.stream() {
self.process_token_tree(inner);
}
let close = self.make_pending_token(
GraphQLTokenKind::SquareBracketClose,
span,
);
self.pending.push_back(close);
},
Delimiter::Parenthesis => {
let open = self.make_pending_token(
GraphQLTokenKind::ParenOpen,
span,
);
self.pending.push_back(open);
for inner in group.stream() {
self.process_token_tree(inner);
}
let close = self.make_pending_token(
GraphQLTokenKind::ParenClose,
span,
);
self.pending.push_back(close);
},
Delimiter::None => {
for inner in group.stream() {
self.process_token_tree(inner);
}
},
}
}
fn process_ident_token(&mut self, ident: Ident) {
let span = ident.span();
let name = ident.to_string();
let kind = match name.as_str() {
"true" => GraphQLTokenKind::True,
"false" => GraphQLTokenKind::False,
"null" => GraphQLTokenKind::Null,
_ => GraphQLTokenKind::name_owned(name),
};
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
}
fn process_punct_token(&mut self, punct: Punct) {
let span = punct.span();
let ch = punct.as_char();
match ch {
'.' => self.process_dot_punct(span),
'-' => {
let kind = GraphQLTokenKind::error(
PENDING_MINUS_ERROR_MSG,
smallvec![],
);
let token =
self.make_pending_token(kind, span);
self.pending.push_back(token);
},
'!' => {
let token = self.make_pending_token(
GraphQLTokenKind::Bang,
span,
);
self.pending.push_back(token);
},
'$' => {
let token = self.make_pending_token(
GraphQLTokenKind::Dollar,
span,
);
self.pending.push_back(token);
},
'&' => {
let token = self.make_pending_token(
GraphQLTokenKind::Ampersand,
span,
);
self.pending.push_back(token);
},
':' => {
let token = self.make_pending_token(
GraphQLTokenKind::Colon,
span,
);
self.pending.push_back(token);
},
'=' => {
let token = self.make_pending_token(
GraphQLTokenKind::Equals,
span,
);
self.pending.push_back(token);
},
'@' => {
let token = self.make_pending_token(
GraphQLTokenKind::At,
span,
);
self.pending.push_back(token);
},
'|' => {
let token = self.make_pending_token(
GraphQLTokenKind::Pipe,
span,
);
self.pending.push_back(token);
},
',' => {
let byte_span = self.make_byte_span(&span);
self.pending_trivia
.push(GraphQLTriviaToken::Comma {
span: byte_span,
});
},
_ => {
let kind = GraphQLTokenKind::error(
format!("Unexpected character `{ch}`"),
smallvec![],
);
let token =
self.make_pending_token(kind, span);
self.pending.push_back(token);
},
}
}
fn process_dot_punct(&mut self, span: Span) {
let is_single_dot_error = |pt: &PendingToken| {
matches!(
&pt.kind,
GraphQLTokenKind::Error(err)
if err.message == DOT_ERROR_MSG
)
};
let is_double_dot_error = |pt: &PendingToken| {
matches!(
&pt.kind,
GraphQLTokenKind::Error(err)
if err.message == DOUBLE_DOT_ERROR_MSG
)
};
let is_spaced_double_dot_error =
|pt: &PendingToken| {
matches!(
&pt.kind,
GraphQLTokenKind::Error(err)
if err.message
== SPACED_DOT_DOT_ERROR_MSG
)
};
if let Some(last) = self.pending.back() {
if is_double_dot_error(last) {
let last_end = last
.ending_span
.as_ref()
.unwrap_or(&last.span);
if Self::spans_are_adjacent(last_end, &span) {
let prev =
self.pending.pop_back().unwrap();
self.pending.push_back(PendingToken {
kind: GraphQLTokenKind::Ellipsis,
preceding_trivia:
prev.preceding_trivia,
span: prev.span,
ending_span: Some(span),
});
return;
} else if Self::spans_on_same_line(
last_end, &span,
) {
let note_span =
self.make_source_span(&span);
let prev =
self.pending.pop_back().unwrap();
self.pending.push_back(PendingToken {
kind: GraphQLTokenKind::error(
"Unexpected `.. .`",
smallvec![
GraphQLErrorNote::help_with_span(
"This `.` may have been \
intended to complete a \
`...` spread operator. \
Try removing the extra \
spacing between the \
dots.",
note_span,
)
],
),
preceding_trivia:
prev.preceding_trivia,
span: prev.span,
ending_span: Some(span),
});
return;
} else {
let kind = GraphQLTokenKind::error(
DOT_ERROR_MSG,
smallvec![],
);
let token =
self.make_pending_token(kind, span);
self.pending.push_back(token);
return;
}
}
if is_spaced_double_dot_error(last) {
let last_end = last
.ending_span
.as_ref()
.unwrap_or(&last.span);
if Self::spans_on_same_line(
last_end, &span,
) {
let note_span =
self.make_source_span(&span);
let prev =
self.pending.pop_back().unwrap();
self.pending.push_back(PendingToken {
kind: GraphQLTokenKind::error(
"Unexpected `. . .`",
smallvec![
GraphQLErrorNote::help_with_span(
"These dots may have \
been intended to form \
a `...` spread \
operator. Try removing \
the extra spacing \
between the dots.",
note_span,
)
],
),
preceding_trivia:
prev.preceding_trivia,
span: prev.span,
ending_span: Some(span),
});
return;
} else {
let kind = GraphQLTokenKind::error(
DOT_ERROR_MSG,
smallvec![],
);
let token =
self.make_pending_token(kind, span);
self.pending.push_back(token);
return;
}
}
if is_single_dot_error(last) {
if Self::spans_are_adjacent(
&last.span, &span,
) {
let prev =
self.pending.pop_back().unwrap();
self.pending.push_back(PendingToken {
kind: GraphQLTokenKind::error(
DOUBLE_DOT_ERROR_MSG,
smallvec![
GraphQLErrorNote::help(
"Add one more `.` to \
form the spread \
operator `...`",
),
],
),
preceding_trivia:
prev.preceding_trivia,
span: prev.span,
ending_span: Some(span),
});
return;
} else if Self::spans_on_same_line(
&last.span, &span,
) {
let note_span =
self.make_source_span(&span);
let prev =
self.pending.pop_back().unwrap();
self.pending.push_back(PendingToken {
kind: GraphQLTokenKind::error(
SPACED_DOT_DOT_ERROR_MSG,
smallvec![
GraphQLErrorNote::help_with_span(
"These dots may have \
been intended to form \
a `...` spread \
operator. Try removing \
the extra spacing \
between the dots.",
note_span,
)
],
),
preceding_trivia:
prev.preceding_trivia,
span: prev.span,
ending_span: Some(span),
});
return;
}
}
}
let kind =
GraphQLTokenKind::error(DOT_ERROR_MSG, smallvec![]);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
}
fn process_literal_token(&mut self, lit: Literal) {
let span = lit.span();
let lit_str = lit.to_string();
if Self::is_raw_string(&lit_str) {
self.process_raw_string_error(&lit_str, span);
return;
}
if lit_str.parse::<i64>().is_ok() {
let kind =
GraphQLTokenKind::int_value_owned(lit_str);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
return;
}
if lit_str.parse::<f64>().is_ok() {
let kind =
GraphQLTokenKind::float_value_owned(lit_str);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
return;
}
if lit_str.starts_with('"') && lit_str.ends_with('"') {
let kind =
GraphQLTokenKind::string_value_owned(lit_str);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
return;
}
let kind = GraphQLTokenKind::name_owned(lit_str);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
}
fn process_raw_string_error(
&mut self,
lit_str: &str,
span: Span,
) {
let content =
Self::extract_raw_string_content(lit_str);
let suggestion =
Self::suggest_graphql_string(&content);
let note_span = self.make_source_span(&span);
let kind = GraphQLTokenKind::error(
"Rust raw strings (`r\"...\"` or `r#\"...\"#`) \
are not valid GraphQL syntax"
.to_string(),
smallvec![GraphQLErrorNote::help_with_span(
format!("Consider using: {suggestion}"),
note_span,
)],
);
let token = self.make_pending_token(kind, span);
self.pending.push_back(token);
}
fn extract_raw_string_content(lit_str: &str) -> String {
let after_r = &lit_str[1..];
let hash_count =
after_r.chars().take_while(|&c| c == '#').count();
let start = 1 + hash_count + 1; let end = lit_str.len() - hash_count - 1;
if start < end {
lit_str[start..end].to_string()
} else {
String::new()
}
}
fn suggest_graphql_string(content: &str) -> String {
let newline_count = content
.chars()
.filter(|&c| c == '\n')
.count()
+ content.chars().filter(|&c| c == '\r').count();
let quote_count =
content.chars().filter(|&c| c == '"').count();
let needs_block_string =
newline_count > 4 || quote_count > 4;
if needs_block_string {
let escaped =
content.replace("\"\"\"", "\\\"\"\"");
format!("\"\"\"{escaped}\"\"\"")
} else {
let escaped = content
.replace('\\', "\\\\")
.replace('"', "\\\"");
format!("\"{escaped}\"")
}
}
fn try_combine_block_string(
&mut self,
) -> Option<PendingToken> {
if self.pending.len() < 3 {
return None;
}
let (s1, span1, s2, span2, s3, span3) = match (
&self.pending[0],
&self.pending[1],
&self.pending[2],
) {
(
PendingToken {
kind: GraphQLTokenKind::StringValue(s1),
span: span1,
..
},
PendingToken {
kind: GraphQLTokenKind::StringValue(s2),
span: span2,
..
},
PendingToken {
kind: GraphQLTokenKind::StringValue(s3),
span: span3,
..
},
) => (
s1.clone(),
*span1,
s2.clone(),
*span2,
s3.clone(),
*span3,
),
_ => return None,
};
if s1 != "\"\"" || s3 != "\"\"" {
return None;
}
if !Self::spans_are_adjacent(&span1, &span2)
|| !Self::spans_are_adjacent(&span2, &span3)
{
return None;
}
let content = if s2.len() >= 2 {
&s2[1..s2.len() - 1]
} else {
""
};
let block_string =
format!("\"\"\"{content}\"\"\"");
let trivia = std::mem::take(
&mut self.pending[0].preceding_trivia,
);
self.pending.drain(0..3);
Some(PendingToken {
kind: GraphQLTokenKind::string_value_owned(
block_string,
),
preceding_trivia: trivia,
span: span1,
ending_span: Some(span3),
})
}
fn try_combine_negative_number(
&mut self,
) -> Option<PendingToken> {
if self.pending.len() < 2 {
return None;
}
let is_minus_error = matches!(
&self.pending[0].kind,
GraphQLTokenKind::Error(err)
if err.message == PENDING_MINUS_ERROR_MSG
);
if !is_minus_error {
return None;
}
match &self.pending[1].kind {
GraphQLTokenKind::IntValue(value) => {
let negative_value = format!("-{value}");
let minus_span = self.pending[0].span;
let number_span = self.pending[1].span;
let trivia = std::mem::take(
&mut self.pending[0].preceding_trivia,
);
self.pending.drain(0..2);
Some(PendingToken {
kind:
GraphQLTokenKind::int_value_owned(
negative_value,
),
preceding_trivia: trivia,
span: minus_span,
ending_span: Some(number_span),
})
},
GraphQLTokenKind::FloatValue(value) => {
let negative_value = format!("-{value}");
let minus_span = self.pending[0].span;
let number_span = self.pending[1].span;
let trivia = std::mem::take(
&mut self.pending[0].preceding_trivia,
);
self.pending.drain(0..2);
Some(PendingToken {
kind:
GraphQLTokenKind::float_value_owned(
negative_value,
),
preceding_trivia: trivia,
span: minus_span,
ending_span: Some(number_span),
})
},
_ => None,
}
}
fn make_eof_token(&mut self) -> GraphQLToken<'static> {
let span = match self.last_span {
Some(s) => self.make_byte_span(&s),
None => ByteSpan::new(0, 0),
};
let trivia =
std::mem::take(&mut self.pending_trivia);
GraphQLToken {
kind: GraphQLTokenKind::Eof,
preceding_trivia: trivia,
span,
}
}
}