use super::LexerMode;
use super::adapter;
use super::lex;
use crate::ModuleId;
use crate::errors::KclError;
use crate::parsing::parse_str;
fn characterize_old(source: &str) -> String {
let _guard = LexerMode::override_for_test(LexerMode::Old);
let module_id = ModuleId::default();
let fmt_err = |err: &KclError| {
format!(
"Err [{}] {:?} @ {:?}",
err.error_type(),
err.message(),
err.source_ranges()
)
};
let lex_stage = match lex(source, module_id) {
Ok(stream) => format!("Ok ({} tokens)", stream.iter().count()),
Err(err) => fmt_err(&err),
};
let parse_stage = match parse_str(source, module_id).0 {
Ok((program, issues)) => {
let issues = issues
.iter()
.map(|i| format!("{:?} {:?} @ {:?}", i.severity, i.message, i.source_range))
.collect::<Vec<_>>();
format!("Ok (program: {}, issues: {issues:?})", program.is_some())
}
Err(err) => fmt_err(&err),
};
format!("source: {source:?}\nlex: {lex_stage}\nparse: {parse_stage}")
}
#[test]
fn row1_unknown_ascii_tilde() {
insta::assert_snapshot!(characterize_old("~"));
}
#[test]
fn row1_unknown_ascii_tilde_in_context() {
insta::assert_snapshot!(characterize_old("12 ~ 8"));
}
#[test]
fn row2_unterminated_string() {
insta::assert_snapshot!(characterize_old("\"abc"));
}
#[test]
fn row3_lone_double_quote() {
insta::assert_snapshot!(characterize_old("\""));
}
#[test]
fn row4_unterminated_block_comment() {
insta::assert_snapshot!(characterize_old("/* abc"));
}
#[test]
fn row5_non_ascii_whitespace_nbsp() {
insta::assert_snapshot!(characterize_old("a\u{00A0}b"));
}
#[test]
fn row6_unsupported_unicode_scalar_emoji() {
insta::assert_snapshot!(characterize_old("a\u{1F642}b"));
}
#[test]
fn row7_ascii_unknown_then_non_ascii() {
insta::assert_snapshot!(characterize_old("~ \u{4E9E}"));
}
#[test]
fn row8_eof_ish_crash_input_1() {
insta::assert_snapshot!(characterize_old("{\"\u{78E}\u{78E}\0\0\0\"\"."));
}
#[test]
fn row8_eof_ish_crash_input_2() {
insta::assert_snapshot!(characterize_old("(/=e\"\u{616}\u{75D}\"\""));
}
fn characterize_new(source: &str) -> String {
let result = adapter::lex_with_diagnostics(source, ModuleId::default());
let final_line = match result.to_lexical_error() {
None => "Ok (no lexical issues)".to_owned(),
Some(err) => format!(
"Err [{}] {:?} @ {:?}",
err.error_type(),
err.message(),
err.source_ranges()
),
};
format!(
"source: {source:?}\ntokens: {}\nissues: {}\nfinal: {final_line}",
result.tokens.iter().count(),
result.issues.len()
)
}
#[test]
fn row1_unknown_ascii_tilde_new() {
insta::assert_snapshot!(characterize_new("~"));
}
#[test]
fn row1_unknown_ascii_tilde_in_context_new() {
insta::assert_snapshot!(characterize_new("12 ~ 8"));
}
#[test]
fn row2_unterminated_string_new() {
insta::assert_snapshot!(characterize_new("\"abc"));
}
#[test]
fn row3_lone_double_quote_new() {
insta::assert_snapshot!(characterize_new("\""));
}
#[test]
fn row4_unterminated_block_comment_new() {
insta::assert_snapshot!(characterize_new("/* abc"));
}
#[test]
fn row5_non_ascii_whitespace_nbsp_new() {
insta::assert_snapshot!(characterize_new("a\u{00A0}b"));
}
#[test]
fn row6_unsupported_unicode_scalar_emoji_new() {
insta::assert_snapshot!(characterize_new("a\u{1F642}b"));
}
#[test]
fn row7_ascii_unknown_then_non_ascii_new() {
insta::assert_snapshot!(characterize_new("~ \u{4E9E}"));
}
#[test]
fn row8_eof_ish_crash_input_1_new() {
insta::assert_snapshot!(characterize_new("{\"\u{78E}\u{78E}\0\0\0\"\"."));
}
#[test]
fn row8_eof_ish_crash_input_2_new() {
insta::assert_snapshot!(characterize_new("(/=e\"\u{616}\u{75D}\"\""));
}
#[test]
fn valid_multiline_string_is_one_string_token_in_both_lexers() {
let source = "\"line one\nline two\"";
let expected = vec![(super::TokenType::String, 0, source.len())];
let legacy: Vec<_> = super::lex_legacy(source, ModuleId::default())
.unwrap()
.iter()
.map(|token| (token.token_type, token.start, token.end))
.collect();
assert_eq!(
legacy, expected,
"legacy should accept a closed multiline string as one String token"
);
let new_result = adapter::lex_with_diagnostics(source, ModuleId::default());
assert!(
new_result.issues.is_empty(),
"new lexer should accept a closed multiline string without lexical issues, got {} issue(s)",
new_result.issues.len()
);
let new: Vec<_> = new_result
.tokens
.iter()
.map(|token| (token.token_type, token.start, token.end))
.collect();
assert_eq!(
new, expected,
"new lexer should tokenize a closed multiline string as one String token"
);
}
#[test]
fn single_line_string_containing_comment_text_agrees_across_lexers() {
let source = "\"// a comment\"";
let expected = vec![(super::TokenType::String, 0, source.len())];
let legacy: Vec<_> = super::lex_legacy(source, ModuleId::default())
.unwrap()
.iter()
.map(|token| (token.token_type, token.start, token.end))
.collect();
let new_result = adapter::lex_with_diagnostics(source, ModuleId::default());
let new: Vec<_> = new_result
.tokens
.iter()
.map(|token| (token.token_type, token.start, token.end))
.collect();
assert_eq!(legacy, expected, "legacy: single-line string is one String token");
assert_eq!(new, expected, "new: single-line string is one String token");
assert!(
new_result.issues.is_empty(),
"new: no lexical issues for a closed single-line string"
);
assert_eq!(legacy, new, "both lexers agree on the single-line case");
}