use crate::ByteSpan;
use crate::SourceMap;
use crate::SourcePosition;
use std::path::PathBuf;
#[test]
fn source_text_empty_string() {
let sm = SourceMap::new_with_source("", None);
let pos = sm.resolve_offset(0);
assert_eq!(pos, Some(SourcePosition::new(0, 0, Some(0), 0)));
}
#[test]
fn source_text_single_line_ascii() {
let sm = SourceMap::new_with_source("hello", None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
assert_eq!(pos.byte_offset(), 0);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 3);
assert_eq!(pos.col_utf16(), Some(3));
assert_eq!(pos.byte_offset(), 3);
let pos = sm.resolve_offset(5).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 5);
assert_eq!(pos.byte_offset(), 5);
}
#[test]
fn source_text_multiline_lf() {
let src = "abc\ndef\nghi";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(4).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(5).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 1);
let pos = sm.resolve_offset(8).unwrap();
assert_eq!(pos.line(), 2);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn source_text_multiline_cr() {
let src = "ab\rcd\ref";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(6).unwrap();
assert_eq!(pos.line(), 2);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn source_text_multiline_crlf() {
let src = "ab\r\ncd\r\nef";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.line(), 0);
let pos = sm.resolve_offset(4).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(8).unwrap();
assert_eq!(pos.line(), 2);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn source_text_mixed_line_terminators() {
let src = "a\nb\rc\r\nd";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.line(), 0);
let pos = sm.resolve_offset(2).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(4).unwrap();
assert_eq!(pos.line(), 2);
assert_eq!(pos.col_utf8(), 0);
let pos = sm.resolve_offset(7).unwrap();
assert_eq!(pos.line(), 3);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn source_text_bom_at_start() {
let src = "\u{FEFF}hello";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 1); assert_eq!(pos.col_utf16(), Some(1)); assert_eq!(pos.byte_offset(), 3);
}
#[test]
fn source_text_emoji() {
let src = "a🎉b";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.col_utf8(), 0);
assert_eq!(pos.col_utf16(), Some(0));
let pos = sm.resolve_offset(1).unwrap();
assert_eq!(pos.col_utf8(), 1);
assert_eq!(pos.col_utf16(), Some(1));
let pos = sm.resolve_offset(5).unwrap();
assert_eq!(pos.col_utf8(), 2); assert_eq!(pos.col_utf16(), Some(3)); }
#[test]
fn source_text_cjk() {
let src = "a中b";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(4).unwrap();
assert_eq!(pos.col_utf8(), 2);
assert_eq!(pos.col_utf16(), Some(2));
}
#[test]
fn source_text_accented_char() {
let src = "café";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.col_utf8(), 3); assert_eq!(pos.col_utf16(), Some(3));
}
#[test]
fn source_text_offset_at_newline() {
let src = "ab\ncd";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(2).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 2);
}
#[test]
fn source_text_offset_at_eof() {
let src = "abc";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.line(), 0);
assert_eq!(pos.col_utf8(), 3);
assert_eq!(pos.byte_offset(), 3);
}
#[test]
fn source_text_offset_out_of_bounds() {
let src = "abc";
let sm = SourceMap::new_with_source(src, None);
assert_eq!(sm.resolve_offset(4), None);
}
#[test]
fn source_text_trailing_newline() {
let src = "abc\n";
let sm = SourceMap::new_with_source(src, None);
let pos = sm.resolve_offset(4).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn source_text_resolve_span() {
let src = "abc\ndef";
let path = PathBuf::from("test.graphql");
let sm = SourceMap::new_with_source(src, Some(path.clone()));
let span = ByteSpan::new(0, 7);
let resolved = sm.resolve_span(span).unwrap();
assert_eq!(resolved.start_inclusive.line(), 0);
assert_eq!(resolved.start_inclusive.col_utf8(), 0);
assert_eq!(resolved.end_exclusive.line(), 1);
assert_eq!(resolved.end_exclusive.col_utf8(), 3);
assert_eq!(resolved.file_path, Some(path));
}
#[test]
fn source_text_resolve_span_no_file() {
let src = "hello";
let sm = SourceMap::new_with_source(src, None);
let span = ByteSpan::new(1, 4);
let resolved = sm.resolve_span(span).unwrap();
assert_eq!(resolved.start_inclusive.col_utf8(), 1);
assert_eq!(resolved.end_exclusive.col_utf8(), 4);
assert_eq!(resolved.file_path, None);
}
#[test]
fn source_text_resolve_span_out_of_bounds() {
let src = "abc";
let sm = SourceMap::new_with_source(src, None);
assert!(sm.resolve_span(ByteSpan::new(0, 10)).is_none());
assert!(sm.resolve_span(ByteSpan::new(10, 20)).is_none());
}
#[test]
fn precomputed_basic_lookup() {
let sm = SourceMap::new_precomputed(
vec![
(0, SourcePosition::new(0, 0, Some(0), 0)),
(5, SourcePosition::new(0, 5, Some(5), 5)),
(10, SourcePosition::new(1, 0, Some(0), 10)),
],
None,
);
let pos = sm.resolve_offset(5).unwrap();
assert_eq!(pos.col_utf8(), 5);
let pos = sm.resolve_offset(7).unwrap();
assert_eq!(pos.col_utf8(), 5);
assert_eq!(pos.byte_offset(), 5);
let pos = sm.resolve_offset(10).unwrap();
assert_eq!(pos.line(), 1);
assert_eq!(pos.col_utf8(), 0);
}
#[test]
fn precomputed_empty_returns_none() {
let sm = SourceMap::new_precomputed(vec![], None);
assert_eq!(sm.resolve_offset(0), None);
}
#[test]
fn precomputed_before_first_entry_returns_none() {
let sm = SourceMap::new_precomputed(
vec![(10, SourcePosition::new(1, 0, None, 10))],
None,
);
assert_eq!(sm.resolve_offset(5), None);
}
#[test]
fn precomputed_preserves_utf16_columns() {
let sm = SourceMap::new_precomputed(
vec![
(0, SourcePosition::new(0, 0, Some(0), 0)),
(3, SourcePosition::new(0, 2, Some(3), 3)),
],
None,
);
let pos = sm.resolve_offset(3).unwrap();
assert_eq!(pos.col_utf16(), Some(3));
}
#[test]
fn precomputed_no_utf16() {
let sm = SourceMap::new_precomputed(
vec![(0, SourcePosition::new(0, 0, None, 0))],
None,
);
let pos = sm.resolve_offset(0).unwrap();
assert_eq!(pos.col_utf16(), None);
}
#[test]
fn precomputed_resolve_span() {
let path = PathBuf::from("macro_input.rs");
let sm = SourceMap::new_precomputed(
vec![
(0, SourcePosition::new(0, 0, None, 0)),
(5, SourcePosition::new(0, 5, None, 5)),
],
Some(path.clone()),
);
let resolved = sm.resolve_span(ByteSpan::new(0, 5)).unwrap();
assert_eq!(resolved.start_inclusive.col_utf8(), 0);
assert_eq!(resolved.end_exclusive.col_utf8(), 5);
assert_eq!(resolved.file_path, Some(path));
}
#[test]
fn source_text_resolve_offset_non_char_boundary_returns_none() {
let src = "café";
let sm = SourceMap::new_with_source(src, None);
let result = sm.resolve_offset(4);
assert!(
result.is_none(),
"resolve_offset on a non-char-boundary byte should return None, \
not panic. Got: {result:?}",
);
}
#[test]
fn source_accessor() {
let src = "hello";
let sm_source = SourceMap::new_with_source(src, None);
assert_eq!(sm_source.source(), Some("hello"));
let sm_precomputed = SourceMap::new_precomputed(vec![], None);
assert_eq!(sm_precomputed.source(), None);
}
#[test]
fn file_path_accessor() {
let path = PathBuf::from("schema.graphql");
let sm = SourceMap::new_with_source(
"",
Some(path.clone()),
);
assert_eq!(sm.file_path(), Some(path.as_path()));
let sm_no_path = SourceMap::new_with_source("", None);
assert_eq!(sm_no_path.file_path(), None);
}
#[test]
fn source_map_resolves_all_lexer_byte_offsets() {
use crate::token::GraphQLTokenSource;
use crate::token::StrGraphQLTokenSource;
let sources = &[
"type Query { hello: String }",
"type Query {\n hello: String\n world: Int\n}",
"# comment\ntype Foo { bar: ID! }",
"\"description\"\ntype Bar { baz: [String!]! }",
"type T {\n emoji: String # 🎉\n}",
"type T {\r\n field: Int\r\n}",
"type T {\r field: Int\r}",
];
for &src in sources {
let (tokens, source_map) =
StrGraphQLTokenSource::new(src).collect_with_source_map();
for token in &tokens {
let start = source_map
.resolve_offset(token.span.start)
.unwrap_or_else(|| {
panic!(
"start offset {} should resolve in {:?}",
token.span.start, src,
)
});
let end = source_map
.resolve_offset(token.span.end)
.unwrap_or_else(|| {
panic!(
"end offset {} should resolve in {:?}",
token.span.end, src,
)
});
assert!(
start.line() < end.line()
|| (start.line() == end.line()
&& start.col_utf8() <= end.col_utf8()),
"start ({},{}) must be <= end ({},{}) for token \
at bytes {}..{} in {:?}",
start.line(),
start.col_utf8(),
end.line(),
end.col_utf8(),
token.span.start,
token.span.end,
src,
);
assert!(
start.col_utf16().is_some(),
"UTF-16 col missing for start at byte {} in {:?}",
token.span.start,
src,
);
assert!(
end.col_utf16().is_some(),
"UTF-16 col missing for end at byte {} in {:?}",
token.span.end,
src,
);
}
}
}