use links_notation::{parse_lino, parse_lino_to_links, ParseError, SyntaxError};
fn syntax_error(document: &str) -> SyntaxError {
match parse_lino(document) {
Ok(parsed) => panic!("expected {document:?} not to parse, got {parsed}"),
Err(ParseError::SyntaxError(error)) => error,
Err(other) => panic!("expected a syntax error for {document:?}, got {other}"),
}
}
#[test]
fn test_reports_the_line_and_column_of_the_defect() {
let error = syntax_error("ci_gate x\nstage: rust: nextest\nnext stage\n clippy");
assert_eq!(error.line, 2);
assert_eq!(error.column, 12);
assert_eq!(error.found, Some(':'));
}
#[test]
fn test_offset_agrees_with_the_javascript_port() {
let error = syntax_error("ci_gate x\nstage: rust: nextest\n");
assert_eq!(error.offset, 21);
assert_eq!(error.line, 2);
assert_eq!(error.column, 12);
}
#[test]
fn test_points_at_the_defect_rather_than_at_the_line_it_starts_on() {
let error = syntax_error("ci_gate x\nstage: rust: nextest\n");
assert_ne!(error.column, 1);
assert_eq!(&error.line_text[error.column - 1..error.column], ":");
}
#[test]
fn test_reports_the_line_a_late_defect_is_on() {
let error = syntax_error("a\nb\nc\nd\ne: f: g\nh\n");
assert_eq!(error.line, 5);
assert_eq!(error.column, 5);
assert_eq!(error.line_text, "e: f: g");
}
#[test]
fn test_says_what_could_have_stood_there() {
let error = syntax_error("a: b: c");
assert_eq!(error.expected, vec!["\"(\"", "a reference", "end of line"]);
}
#[test]
fn test_reports_the_end_of_the_document_when_a_group_is_never_closed() {
let error = syntax_error("a (b\n");
assert_eq!(error.offset, 5);
assert_eq!(error.line, 2);
assert_eq!(error.column, 1);
assert_eq!(error.found, None);
assert!(error.expected.contains(&"\")\"".to_string()));
}
#[test]
fn test_reports_an_unmatched_closing_parenthesis() {
let error = syntax_error("a b)\n");
assert_eq!(error.offset, 3);
assert_eq!(error.line, 1);
assert_eq!(error.column, 4);
assert_eq!(error.found, Some(')'));
}
#[test]
fn test_summary_reads_as_a_sentence() {
let error = syntax_error("ci_gate x\nstage: rust: nextest\n");
assert_eq!(
error.summary(),
r#"line 2, column 12: expected "(", a reference or end of line, found ":""#
);
}
#[test]
fn test_snippet_points_a_caret_at_the_offending_character() {
let error = syntax_error("ci_gate x\nstage: rust: nextest\n");
assert_eq!(
error.snippet(),
"2 | stage: rust: nextest\n | ^"
);
}
#[test]
fn test_message_quotes_one_line_rather_than_the_rest_of_the_document() {
let tail = "trailing line\n".repeat(500);
let document = format!("ci_gate x\nstage: rust: nextest\n{tail}");
let message = format!("{}", parse_lino(&document).unwrap_err());
assert!(message.contains("line 2, column 12"), "{message}");
assert!(!message.contains("trailing line"), "{message}");
assert!(message.len() < 200, "message is {} bytes", message.len());
}
#[test]
fn test_message_of_a_long_line_stays_a_message() {
let document = format!("{}: {}: c", "a".repeat(400), "b".repeat(400));
let error = syntax_error(&document);
let message = format!("{error}");
assert_eq!(error.line, 1);
assert_eq!(error.column, 803);
assert!(message.contains("..."), "{message}");
assert!(message.len() < 300, "message is {} bytes", message.len());
let caret = message.lines().last().unwrap().find('^').unwrap();
let quoted = message.lines().nth(1).unwrap();
assert_eq!("ed[caret..caret + 1], ":");
}
#[test]
fn test_error_display_starts_with_the_position() {
let error = parse_lino("a: b: c").unwrap_err();
assert!(
format!("{error}").starts_with("Syntax error at line 1, column 5:"),
"{error}"
);
}
#[test]
fn test_nom_internals_stay_out_of_the_message() {
let message = format!("{}", parse_lino("a: b: c").unwrap_err());
for internal in ["ErrorKind", "code:", "Eof", "Verify", "TakeWhile1"] {
assert!(!message.contains(internal), "{message} mentions {internal}");
}
}
#[test]
fn test_both_entry_points_report_the_same_position() {
let document = "ci_gate x\nstage: rust: nextest\n";
let one = syntax_error(document);
let Err(ParseError::SyntaxError(many)) = parse_lino_to_links(document) else {
panic!("expected a syntax error")
};
assert_eq!(one, many);
}
#[test]
fn test_column_counts_characters_rather_than_bytes() {
let error = syntax_error("привет: b: c");
assert_eq!(error.line, 1);
assert_eq!(error.column, 10);
assert_eq!(error.offset, 15);
assert_eq!(error.found, Some(':'));
}
#[test]
fn test_a_document_that_parses_reports_nothing() {
assert!(parse_lino("a: b\n c: d\n").is_ok());
}