use crate::SourceMap;
use crate::token::GraphQLTokenKind;
use crate::token::GraphQLTokenSource;
use crate::token::StrGraphQLTokenSource;
fn token_kinds(source: &str) -> Vec<GraphQLTokenKind<'_>> {
StrGraphQLTokenSource::new(source)
.map(|t| t.kind)
.collect()
}
#[test]
fn test_punctuators() {
let kinds = token_kinds("{ } ( ) [ ] : = @ ! $ & |");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::CurlyBraceOpen,
GraphQLTokenKind::CurlyBraceClose,
GraphQLTokenKind::ParenOpen,
GraphQLTokenKind::ParenClose,
GraphQLTokenKind::SquareBracketOpen,
GraphQLTokenKind::SquareBracketClose,
GraphQLTokenKind::Colon,
GraphQLTokenKind::Equals,
GraphQLTokenKind::At,
GraphQLTokenKind::Bang,
GraphQLTokenKind::Dollar,
GraphQLTokenKind::Ampersand,
GraphQLTokenKind::Pipe,
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn test_ellipsis() {
let kinds = token_kinds("...");
assert_eq!(
kinds,
vec![GraphQLTokenKind::Ellipsis, GraphQLTokenKind::Eof]
);
}
#[test]
fn punctuators_adjacent_without_whitespace() {
let kinds = token_kinds("{}[]()");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::CurlyBraceOpen,
GraphQLTokenKind::CurlyBraceClose,
GraphQLTokenKind::SquareBracketOpen,
GraphQLTokenKind::SquareBracketClose,
GraphQLTokenKind::ParenOpen,
GraphQLTokenKind::ParenClose,
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn ellipsis_with_surrounding_whitespace() {
let kinds = token_kinds(" ... ");
assert_eq!(
kinds,
vec![GraphQLTokenKind::Ellipsis, GraphQLTokenKind::Eof]
);
}
#[test]
fn ellipsis_followed_by_dot() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("....").collect();
assert_eq!(tokens.len(), 3);
assert!(matches!(tokens[0].kind, GraphQLTokenKind::Ellipsis));
assert!(matches!(
&tokens[1].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.`")
));
assert!(matches!(tokens[2].kind, GraphQLTokenKind::Eof));
}
#[test]
fn test_names() {
let kinds = token_kinds("hello _private type2 __typename");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("hello"),
GraphQLTokenKind::name_borrowed("_private"),
GraphQLTokenKind::name_borrowed("type2"),
GraphQLTokenKind::name_borrowed("__typename"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn name_uppercase() {
let kinds = token_kinds("SCREAMING_CASE ALL_CAPS");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("SCREAMING_CASE"),
GraphQLTokenKind::name_borrowed("ALL_CAPS"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn name_mixed_case() {
let kinds = token_kinds("camelCase PascalCase mixedCase123");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("camelCase"),
GraphQLTokenKind::name_borrowed("PascalCase"),
GraphQLTokenKind::name_borrowed("mixedCase123"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn name_cannot_start_with_digit() {
let kinds = token_kinds("2fast");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("2"),
GraphQLTokenKind::name_borrowed("fast"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn name_single_underscore() {
let kinds = token_kinds("_");
assert_eq!(
kinds,
vec![GraphQLTokenKind::name_borrowed("_"), GraphQLTokenKind::Eof,]
);
}
#[test]
fn name_very_long() {
let long_name = "a".repeat(10000);
let source = long_name.clone();
let kinds = token_kinds(&source);
assert_eq!(kinds.len(), 2);
match &kinds[0] {
GraphQLTokenKind::Name(cow) => assert_eq!(cow.as_ref(), long_name),
_ => panic!("Expected Name token"),
}
}
#[test]
fn name_unicode_rejected() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("café").collect();
assert!(tokens.len() >= 2);
assert_eq!(tokens[0].kind, GraphQLTokenKind::name_borrowed("caf"));
assert!(matches!(&tokens[1].kind, GraphQLTokenKind::Error(_)));
let tokens: Vec<_> = StrGraphQLTokenSource::new("名前").collect();
assert!(matches!(&tokens[0].kind, GraphQLTokenKind::Error(_)));
}
#[test]
fn test_keywords() {
let kinds = token_kinds("true false null");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::True,
GraphQLTokenKind::False,
GraphQLTokenKind::Null,
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn test_keywords_case_sensitive() {
let kinds = token_kinds("True FALSE Null");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("True"),
GraphQLTokenKind::name_borrowed("FALSE"),
GraphQLTokenKind::name_borrowed("Null"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn keyword_case_sensitive_null() {
let kinds = token_kinds("NULL");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("NULL"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn keyword_prefix_trueish() {
let kinds = token_kinds("trueValue");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("trueValue"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn keyword_prefix_falsely() {
let kinds = token_kinds("falsely");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("falsely"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn keyword_prefix_nullable() {
let kinds = token_kinds("nullable");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("nullable"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn test_int_values() {
let kinds = token_kinds("0 123 -456");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("0"),
GraphQLTokenKind::int_value_borrowed("123"),
GraphQLTokenKind::int_value_borrowed("-456"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn int_negative_zero() {
let kinds = token_kinds("-0");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("-0"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn int_negative_leading_zeros_error() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("-007").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("leading zeros")
));
}
#[test]
fn int_max_i32() {
let kinds = token_kinds("2147483647");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("2147483647"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("2147483647").collect();
assert_eq!(
tokens[0].kind.parse_int_value(),
Some(Ok(2147483647_i64))
);
}
#[test]
fn int_min_i32() {
let kinds = token_kinds("-2147483648");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("-2147483648"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("-2147483648").collect();
assert_eq!(
tokens[0].kind.parse_int_value(),
Some(Ok(-2147483648_i64))
);
}
#[test]
fn int_i64_range() {
let kinds = token_kinds("9223372036854775807");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("9223372036854775807"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("9223372036854775807").collect();
assert_eq!(
tokens[0].kind.parse_int_value(),
Some(Ok(i64::MAX))
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("-9223372036854775808").collect();
assert_eq!(
tokens[0].kind.parse_int_value(),
Some(Ok(i64::MIN))
);
}
#[test]
fn int_overflow_i64() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("9223372036854775808").collect();
assert_eq!(
tokens[0].kind,
GraphQLTokenKind::int_value_borrowed("9223372036854775808")
);
let result = tokens[0].kind.parse_int_value();
assert!(matches!(result, Some(Err(_))));
}
#[test]
fn int_underflow_i64() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("-9223372036854775809").collect();
assert_eq!(
tokens[0].kind,
GraphQLTokenKind::int_value_borrowed("-9223372036854775809")
);
let result = tokens[0].kind.parse_int_value();
assert!(matches!(result, Some(Err(_))));
}
#[test]
fn int_followed_by_name() {
let kinds = token_kinds("123abc");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::int_value_borrowed("123"),
GraphQLTokenKind::name_borrowed("abc"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn int_followed_by_dot_name() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("123.abc").collect();
assert!(tokens.len() >= 3);
let first_is_valid =
matches!(&tokens[0].kind, GraphQLTokenKind::IntValue(_)) ||
matches!(&tokens[0].kind, GraphQLTokenKind::Error(_));
assert!(first_is_valid);
}
#[test]
fn test_float_values() {
let kinds = token_kinds("1.5 -3.14 1e10 1.23e-4");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("1.5"),
GraphQLTokenKind::float_value_borrowed("-3.14"),
GraphQLTokenKind::float_value_borrowed("1e10"),
GraphQLTokenKind::float_value_borrowed("1.23e-4"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn float_exponent_uppercase() {
let kinds = token_kinds("1E10 2E3 1.5E-2");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("1E10"),
GraphQLTokenKind::float_value_borrowed("2E3"),
GraphQLTokenKind::float_value_borrowed("1.5E-2"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn float_exponent_positive() {
let kinds = token_kinds("1e+10 2.5E+3");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("1e+10"),
GraphQLTokenKind::float_value_borrowed("2.5E+3"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn float_zero_decimal() {
let kinds = token_kinds("0.0");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("0.0"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("0.0").collect();
assert_eq!(tokens[0].kind.parse_float_value(), Some(Ok(0.0)));
}
#[test]
fn float_leading_zero_decimal() {
let kinds = token_kinds("0.123 0.001 0.999");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("0.123"),
GraphQLTokenKind::float_value_borrowed("0.001"),
GraphQLTokenKind::float_value_borrowed("0.999"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn float_no_leading_zero_error() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(".5").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.`")
));
}
#[test]
fn float_trailing_dot_not_float() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("5.a").collect();
assert!(tokens.len() >= 2);
}
#[test]
fn float_double_dot_error() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("1..5").collect();
let has_error = tokens.iter().any(|t| t.kind.is_error());
assert!(has_error, "Expected an error for double dot in number");
}
#[test]
fn float_negative_zero() {
let kinds = token_kinds("-0.0");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("-0.0"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn float_very_small() {
let kinds = token_kinds("1e-308");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("1e-308"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("1e-308").collect();
let result = tokens[0].kind.parse_float_value();
assert!(matches!(result, Some(Ok(v)) if v > 0.0 && v < 1e-300));
}
#[test]
fn float_subnormal() {
let kinds = token_kinds("1e-324");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::float_value_borrowed("1e-324"),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("1e-324").collect();
let result = tokens[0].kind.parse_float_value();
assert!(matches!(result, Some(Ok(_))));
}
#[test]
fn float_exponent_sign_no_digits() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("1e+").collect();
assert!(!tokens.is_empty());
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("exponent")
));
}
#[test]
fn float_infinity_large() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("1e309").collect();
assert_eq!(
tokens[0].kind,
GraphQLTokenKind::float_value_borrowed("1e309")
);
let result = tokens[0].kind.parse_float_value();
assert!(matches!(result, Some(Ok(v)) if v.is_infinite()));
}
#[test]
fn test_single_line_strings() {
let kinds = token_kinds(r#""hello" "world""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed("\"hello\""),
GraphQLTokenKind::string_value_borrowed("\"world\""),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn string_empty() {
let kinds = token_kinds(r#""""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed("\"\""),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok(String::new()))
);
}
#[test]
fn string_escape_quote() {
let kinds = token_kinds(r#""say \"hello\"""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(r#""say \"hello\"""#),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""say \"hello\"""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok(r#"say "hello""#.to_string()))
);
}
#[test]
fn string_escape_backslash() {
let kinds = token_kinds(r#""path\\to\\file""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(r#""path\\to\\file""#),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""path\\to\\file""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok(r#"path\to\file"#.to_string()))
);
}
#[test]
fn string_escape_slash() {
let kinds = token_kinds(r#""a\/b""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(r#""a\/b""#),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\/b""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a/b".to_string()))
);
}
#[test]
fn string_escape_backspace() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\bb""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a\u{0008}b".to_string()))
);
}
#[test]
fn string_escape_formfeed() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\fb""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a\u{000C}b".to_string()))
);
}
#[test]
fn string_escape_newline() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\nb""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a\nb".to_string()))
);
}
#[test]
fn string_escape_carriage_return() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\rb""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a\rb".to_string()))
);
}
#[test]
fn string_escape_tab() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""a\tb""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("a\tb".to_string()))
);
}
#[test]
fn string_escape_unicode_4digit() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""\u0041""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("A".to_string()))
);
}
#[test]
fn string_escape_unicode_bmp() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""\u00E9""#).collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok("é".to_string()))
);
}
#[test]
fn string_escape_unicode_surrogate_pair() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""\uD83D\uDE00""#).collect();
let result = tokens[0].kind.parse_string_value();
assert!(result.is_some());
}
#[test]
fn string_escape_invalid_unicode() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""\uXXXX""#).collect();
let result = tokens[0].kind.parse_string_value();
assert!(matches!(result, Some(Err(_))));
}
#[test]
fn string_escape_incomplete_unicode() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(r#""\u00""#).collect();
let result = tokens[0].kind.parse_string_value();
assert!(matches!(result, Some(Err(_))));
}
#[test]
fn string_control_chars_error() {
let source = "\"hello\tworld\"";
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let first_token = &tokens[0];
assert!(
matches!(first_token.kind, GraphQLTokenKind::StringValue(_)) ||
matches!(first_token.kind, GraphQLTokenKind::Error(_))
);
}
#[test]
fn test_block_strings() {
let kinds = token_kinds(r#""""block string""""#);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed("\"\"\"block string\"\"\""),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn block_string_multiline() {
let source = "\"\"\"line1\nline2\nline3\"\"\"";
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(source),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let parsed = tokens[0].kind.parse_string_value();
assert!(matches!(parsed, Some(Ok(s)) if s.contains('\n')));
}
#[test]
fn block_string_contains_quotes() {
let source = r#""""contains " quote""""#;
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(source),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn block_string_contains_double_quotes() {
let source = r#""""contains "" quotes""""#;
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(source),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn block_string_common_indent_removal() {
let source = "\"\"\"\n line1\n line2\n \"\"\"";
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let parsed = tokens[0].kind.parse_string_value();
assert!(matches!(parsed, Some(Ok(s)) if !s.starts_with(" ")));
}
#[test]
fn block_string_empty() {
let kinds = token_kinds("\"\"\"\"\"\"");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed("\"\"\"\"\"\""),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new("\"\"\"\"\"\"").collect();
assert_eq!(
tokens[0].kind.parse_string_value(),
Some(Ok(String::new()))
);
}
#[test]
fn block_string_just_whitespace() {
let source = "\"\"\"\n \n \n\"\"\"";
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(source),
GraphQLTokenKind::Eof,
]
);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let parsed = tokens[0].kind.parse_string_value();
assert!(matches!(parsed, Some(Ok(s)) if s.is_empty() || s.chars().all(char::is_whitespace)));
}
#[test]
fn block_string_crlf_handling() {
let source = "\"\"\"line1\r\nline2\r\n\"\"\"";
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
GraphQLTokenKind::string_value_borrowed(source),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn test_comments_as_trivia() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("# comment\nfield").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[0].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comment { value, .. } if value == " comment"
));
assert!(matches!(
&tokens[0].preceding_trivia[1],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == "\n"
));
}
#[test]
fn comment_empty() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("#\nfield").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[0].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comment { value, .. } if value.is_empty()
));
assert!(matches!(
&tokens[0].preceding_trivia[1],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == "\n"
));
}
#[test]
fn comment_contains_hash() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("# contains # hash\nfield").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[0].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comment { value, .. }
if value == " contains # hash"
));
}
#[test]
fn comment_unicode() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("# 日本語コメント 🎉\nfield").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[0].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comment { value, .. }
if value.contains("日本語")
));
}
#[test]
fn test_bom_ignored() {
let kinds = token_kinds("\u{FEFF}name");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("name"),
GraphQLTokenKind::Eof,
]
);
let kinds = token_kinds("a\u{FEFF}b");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("a"),
GraphQLTokenKind::name_borrowed("b"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn whitespace_tab() {
let kinds = token_kinds("a\tb\tc");
assert_eq!(
kinds,
vec![
GraphQLTokenKind::name_borrowed("a"),
GraphQLTokenKind::name_borrowed("b"),
GraphQLTokenKind::name_borrowed("c"),
GraphQLTokenKind::Eof,
]
);
}
#[test]
fn multiple_commas() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("field1,,, field2").collect();
assert_eq!(tokens.len(), 3);
assert_eq!(
tokens[1].preceding_trivia.len(),
4,
"Expected trivia on second field"
);
}
#[test]
fn test_invalid_character_recovery() {
let kinds = token_kinds("{ ^ }");
assert_eq!(kinds.len(), 4); assert!(matches!(kinds[0], GraphQLTokenKind::CurlyBraceOpen));
assert!(matches!(kinds[1], GraphQLTokenKind::Error(_)));
assert!(matches!(kinds[2], GraphQLTokenKind::CurlyBraceClose));
assert!(matches!(kinds[3], GraphQLTokenKind::Eof));
}
#[test]
fn invalid_char_tilde() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("~").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains('~') || err.message.contains("Unexpected")
));
}
#[test]
fn invalid_char_backtick() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("`").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains('`') || err.message.contains("Unexpected")
));
}
#[test]
fn invalid_char_question() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("?").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains('?') || err.message.contains("Unexpected")
));
}
#[test]
fn invalid_char_control() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("\0").collect();
assert!(matches!(&tokens[0].kind, GraphQLTokenKind::Error(_)));
let tokens: Vec<_> = StrGraphQLTokenSource::new("\x07").collect();
assert!(matches!(&tokens[0].kind, GraphQLTokenKind::Error(_)));
}
#[test]
fn multiple_errors_collected() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("^ ~ ?").collect();
let error_count =
tokens.iter().filter(|t| t.kind.is_error()).count();
assert!(error_count >= 3, "Expected at least 3 errors, got {error_count}");
}
#[test]
fn test_position_single_line() {
let source = "abc def";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
assert_eq!(tokens.len(), 3);
let pos = sm.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
assert_eq!(tokens[0].span.start, 0);
let pos = sm.resolve_offset(tokens[0].span.end).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 3);
assert_eq!(pos.col_utf16(), Some(3));
assert_eq!(tokens[0].span.end, 3);
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 4);
assert_eq!(pos.col_utf16(), Some(4));
assert_eq!(tokens[1].span.start, 4);
let pos = sm.resolve_offset(tokens[1].span.end).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 7);
assert_eq!(pos.col_utf16(), Some(7));
assert_eq!(tokens[1].span.end, 7);
}
#[test]
fn test_position_multiple_lines() {
let source = "abc\ndef\nghi";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
assert_eq!(tokens.len(), 4);
let pos = sm.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
let pos = sm.resolve_offset(tokens[2].span.start).unwrap();
assert_eq!(pos.line(), 2);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
}
#[test]
fn test_position_crlf_newline() {
let source = "abc\r\ndef";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
assert_eq!(tokens.len(), 3);
let pos = sm.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
}
#[test]
fn test_position_cr_newline() {
let source = "abc\rdef";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
assert_eq!(tokens.len(), 3);
let pos = sm.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
}
#[test]
fn test_utf16_column_ascii() {
let source = "abc def";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let pos0 = sm.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos0.col_utf16(), Some(pos0.col_utf8()));
let pos1 = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos1.col_utf16(), Some(pos1.col_utf8()));
}
#[test]
fn test_utf16_column_bmp_characters() {
let source = "α x";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.col_utf8(), 2);
assert_eq!(pos.col_utf16(), Some(2));
}
#[test]
fn test_utf16_column_supplementary_characters() {
let source = "\"🎉\" x";
let sm = SourceMap::new_with_source(source, None);
let tokens: Vec<_> = StrGraphQLTokenSource::new(source).collect();
let pos = sm.resolve_offset(tokens[1].span.start).unwrap();
assert_eq!(pos.col_utf8(), 4);
assert_eq!(pos.col_utf16(), Some(5));
}
#[test]
fn position_byte_offset() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("α x").collect();
assert_eq!(tokens[0].span.start, 0);
assert_eq!(tokens[0].span.end, 2);
assert_eq!(tokens[1].span.start, 3);
}
#[test]
fn position_with_bom() {
let (tokens, source_map) =
StrGraphQLTokenSource::new("\u{FEFF}name")
.collect_with_source_map();
let pos = source_map.resolve_offset(tokens[0].span.start).unwrap();
assert_eq!(pos.col_utf8(), 1);
assert_eq!(tokens[0].span.start, 3);
}
#[test]
fn position_with_file_path() {
use std::path::Path;
use std::path::PathBuf;
let path = Path::new("test.graphql");
let source = StrGraphQLTokenSource::with_file_path("field", path);
let (_tokens, source_map) = source.collect_with_source_map();
assert_eq!(
source_map.file_path(),
Some(PathBuf::from("test.graphql").as_path()),
);
}
#[test]
fn test_dot_pattern_adjacent_two() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("..").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `..`")
));
}
#[test]
fn test_dot_pattern_spaced_two() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(". .").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `. .`")
));
}
#[test]
fn test_dot_pattern_two_adjacent_one_spaced() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(".. .").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.. .`")
));
}
#[test]
fn test_dot_pattern_one_spaced_two_adjacent() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(". ..").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `. ..`")
));
}
#[test]
fn test_dot_pattern_all_spaced() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(". . .").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `. . .`")
));
}
#[test]
fn test_dot_pattern_single() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(".").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.`")
));
}
#[test]
fn test_dot_pattern_separate_lines() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(".\n.").collect();
assert_eq!(tokens.len(), 3);
assert!(matches!(&tokens[0].kind, GraphQLTokenKind::Error(_)));
assert!(matches!(&tokens[1].kind, GraphQLTokenKind::Error(_)));
}
#[test]
fn test_dot_pattern_two_dots_newline_one_dot() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("..\n.").collect();
assert_eq!(tokens.len(), 3);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `..`")
));
assert!(matches!(
&tokens[1].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.`")
));
}
#[test]
fn test_dot_pattern_one_dot_newline_two_dots() {
let tokens: Vec<_> = StrGraphQLTokenSource::new(".\n..").collect();
assert_eq!(tokens.len(), 3);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `.`")
));
assert!(matches!(
&tokens[1].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unexpected `..`")
));
}
#[test]
fn test_number_leading_zeros() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("007").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("leading zeros")
));
}
#[test]
fn test_number_exponent_no_digits() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("1e").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("exponent")
));
}
#[test]
fn test_number_lone_minus() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("- ").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("`-`")
));
}
#[test]
fn test_string_unterminated() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("\"hello").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated")
));
}
#[test]
fn test_string_unescaped_newline() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("\"hello\nworld\"").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated")
));
}
#[test]
fn test_string_unescaped_bare_cr() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("\"hello\rworld\"").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated")
));
}
#[test]
fn test_string_unescaped_crlf() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("\"hello\r\nworld\"").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated")
));
}
#[test]
fn test_string_unescaped_bare_cr_at_eof() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("\"hello\r").collect();
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated")
));
}
#[test]
fn test_block_string_unterminated() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("\"\"\"hello").collect();
assert_eq!(tokens.len(), 2);
assert!(matches!(
&tokens[0].kind,
GraphQLTokenKind::Error(err)
if err.message.contains("Unterminated block string")
));
}
#[test]
fn test_block_string_escaped_triple_quote() {
let kinds = token_kinds(r#""""hello \""" world""""#);
assert_eq!(kinds.len(), 2);
assert_eq!(
kinds[0],
GraphQLTokenKind::string_value_borrowed(r#""""hello \""" world""""#)
);
}
#[test]
fn test_comma_as_trivia() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("a, b").collect();
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[1].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[1].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comma { .. }
));
assert!(matches!(
&tokens[1].preceding_trivia[1],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == " "
));
}
#[test]
fn test_multiple_comments_as_trivia() {
let tokens: Vec<_> =
StrGraphQLTokenSource::new("# first\n# second\nfield").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].preceding_trivia.len(), 4);
assert!(matches!(
&tokens[0].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Comment { value, .. } if value == " first"
));
assert!(matches!(
&tokens[0].preceding_trivia[1],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == "\n"
));
assert!(matches!(
&tokens[0].preceding_trivia[2],
crate::token::GraphQLTriviaToken::Comment { value, .. } if value == " second"
));
assert!(matches!(
&tokens[0].preceding_trivia[3],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == "\n"
));
}
#[test]
fn test_trailing_comment_on_eof() {
let tokens: Vec<_> = StrGraphQLTokenSource::new("field # trailing").collect();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[1].preceding_trivia.len(), 2);
assert!(matches!(
&tokens[1].preceding_trivia[0],
crate::token::GraphQLTriviaToken::Whitespace { value, .. } if value == " "
));
assert!(matches!(
&tokens[1].preceding_trivia[1],
crate::token::GraphQLTriviaToken::Comment { value, .. } if value == " trailing"
));
}