diff --git a/Cargo.toml b/Cargo.toml index 063d52211d..043f977527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ file = "1.1.1" ron = "0.1.5" [dev-dependencies] -difference = "1.0.0" +testutils = { path = "./tests/testutils" } \ No newline at end of file diff --git a/src/parser/event_parser/grammar.rs b/src/parser/event_parser/grammar.rs index 79ef8b31c1..e1f717714a 100644 --- a/src/parser/event_parser/grammar.rs +++ b/src/parser/event_parser/grammar.rs @@ -8,7 +8,12 @@ pub fn file(p: &mut Parser) { node(p, FILE, |p| { shebang(p); inner_attributes(p); - many(p, |p| skip_to_first(p, item_first, item)); + many(p, |p| { + skip_to_first( + p, item_first, item, + "expected item", + ) + }); }) } @@ -84,19 +89,34 @@ fn comma_list bool>(p: &mut Parser, f: F) { } -fn skip_to_first(p: &mut Parser, cond: C, f: F) -> bool +fn skip_to_first(p: &mut Parser, cond: C, f: F, message: &str) -> bool where C: Fn(&Parser) -> bool, F: FnOnce(&mut Parser), { + let mut skipped = false; loop { if cond(p) { + if skipped { + p.finish(); + } f(p); return true; } - if p.bump().is_none() { + if p.is_eof() { + if skipped { + p.finish(); + } return false; } + if !skipped { + p.start(ERROR); + p.error() + .message(message) + .emit(); + } + p.bump().unwrap(); + skipped = true; } } diff --git a/src/tree/file_builder.rs b/src/tree/file_builder.rs index b07f4027b4..37bd5b2c87 100644 --- a/src/tree/file_builder.rs +++ b/src/tree/file_builder.rs @@ -73,7 +73,9 @@ impl FileBuilder { pub fn finish(self) -> File { assert!( self.in_progress.is_empty(), - "some nodes in FileBuilder are unfinished" + "some nodes in FileBuilder are unfinished: {:?}", + self.in_progress.iter().map(|&(idx, _)| self.nodes[idx].kind) + .collect::>() ); assert!( self.pos == (self.text.len() as u32).into(), @@ -122,11 +124,6 @@ impl FileBuilder { let idx = self.current_id(); &mut self.nodes[idx] } - - fn current_sibling(&mut self) -> Option<&mut NodeData> { - let idx = self.in_progress.last().unwrap().1?; - Some(&mut self.nodes[idx]) - } } fn fill(slot: &mut Option, value: T) { diff --git a/tests/data/parser/err/0001_item_recovery_in_file.rs b/tests/data/parser/err/0001_item_recovery_in_file.rs new file mode 100644 index 0000000000..98f23de1f2 --- /dev/null +++ b/tests/data/parser/err/0001_item_recovery_in_file.rs @@ -0,0 +1,3 @@ +if match + +struct S {} \ No newline at end of file diff --git a/tests/data/parser/err/0001_item_recovery_in_file.txt b/tests/data/parser/err/0001_item_recovery_in_file.txt new file mode 100644 index 0000000000..730367694b --- /dev/null +++ b/tests/data/parser/err/0001_item_recovery_in_file.txt @@ -0,0 +1,14 @@ +FILE@[0; 21) + ERROR@[0; 10) + err: `expected item` + IDENT@[0; 2) + WHITESPACE@[2; 3) + IDENT@[3; 8) + WHITESPACE@[8; 10) + STRUCT_ITEM@[10; 21) + STRUCT_KW@[10; 16) + WHITESPACE@[16; 17) + IDENT@[17; 18) + WHITESPACE@[18; 19) + L_CURLY@[19; 20) + R_CURLY@[20; 21) diff --git a/tests/lexer.rs b/tests/lexer.rs index beca19c24c..6c7531596e 100644 --- a/tests/lexer.rs +++ b/tests/lexer.rs @@ -1,56 +1,31 @@ extern crate file; -#[macro_use(assert_diff)] -extern crate difference; extern crate libsyntax2; +extern crate testutils; -use std::path::{PathBuf, Path}; -use std::fs::read_dir; +use std::path::{Path}; use std::fmt::Write; use libsyntax2::{Token, tokenize}; +use testutils::{assert_equal_text, collect_tests}; #[test] fn lexer_tests() { - for test_case in lexer_test_cases() { + for test_case in collect_tests(&["lexer"]) { lexer_test_case(&test_case); } } -fn lexer_test_dir() -> PathBuf { - let dir = env!("CARGO_MANIFEST_DIR"); - PathBuf::from(dir).join("tests/data/lexer") -} - -fn lexer_test_cases() -> Vec { - let mut acc = Vec::new(); - let dir = lexer_test_dir(); - for file in read_dir(&dir).unwrap() { - let file = file.unwrap(); - let path = file.path(); - if path.extension().unwrap_or_default() == "rs" { - acc.push(path); - } - } - acc.sort(); - acc -} - fn lexer_test_case(path: &Path) { let actual = { let text = file::get_text(path).unwrap(); let tokens = tokenize(&text); dump_tokens(&tokens, &text) }; - let expected = file::get_text(&path.with_extension("txt")).unwrap(); + let path = path.with_extension("txt"); + let expected = file::get_text(&path).unwrap(); let expected = expected.as_str(); let actual = actual.as_str(); - if expected == actual { - return - } - if expected.trim() == actual.trim() { - panic!("Whitespace difference!") - } - assert_diff!(expected, actual, "\n", 0) + assert_equal_text(expected, actual, &path) } fn dump_tokens(tokens: &[Token], text: &str) -> String { diff --git a/tests/parser.rs b/tests/parser.rs index 43d04e4919..518852bb2f 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -1,46 +1,20 @@ extern crate file; -#[macro_use(assert_diff)] -extern crate difference; extern crate libsyntax2; +extern crate testutils; -use std::path::{PathBuf, Path}; -use std::fs::read_dir; +use std::path::{Path}; use std::fmt::Write; use libsyntax2::{tokenize, parse, Node, File}; +use testutils::{collect_tests, assert_equal_text}; #[test] fn parser_tests() { - for test_case in parser_test_cases() { + for test_case in collect_tests(&["parser/ok", "parser/err"]) { parser_test_case(&test_case); } } -fn parser_test_dir() -> PathBuf { - let dir = env!("CARGO_MANIFEST_DIR"); - PathBuf::from(dir).join("tests/data/parser") -} - -fn test_from_dir(dir: &Path) -> Vec { - let mut acc = Vec::new(); - for file in read_dir(&dir).unwrap() { - let file = file.unwrap(); - let path = file.path(); - if path.extension().unwrap_or_default() == "rs" { - acc.push(path); - } - } - acc.sort(); - acc -} - -fn parser_test_cases() -> Vec { - let mut acc = Vec::new(); - acc.extend(test_from_dir(&parser_test_dir().join("ok"))); - acc.extend(test_from_dir(&parser_test_dir().join("err"))); - acc -} - fn parser_test_case(path: &Path) { let actual = { let text = file::get_text(path).unwrap(); @@ -48,19 +22,13 @@ fn parser_test_case(path: &Path) { let file = parse(text, &tokens); dump_tree(&file) }; - let expected = path.with_extension("txt"); - let expected = file::get_text(&expected).expect( - &format!("Can't read {}", expected.display()) + let expected_path = path.with_extension("txt"); + let expected = file::get_text(&expected_path).expect( + &format!("Can't read {}", expected_path.display()) ); let expected = expected.as_str(); let actual = actual.as_str(); - if expected == actual { - return - } - if expected.trim() == actual.trim() { - panic!("Whitespace difference! {}", path.display()) - } - assert_diff!(expected, actual, "\n", 0) + assert_equal_text(expected, actual, &expected_path); } fn dump_tree(file: &File) -> String { diff --git a/tests/testutils/Cargo.toml b/tests/testutils/Cargo.toml new file mode 100644 index 0000000000..9003822eee --- /dev/null +++ b/tests/testutils/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "testutils" +version = "0.1.0" +authors = ["Aleksey Kladov "] + +[dependencies] +file = "1.0" +difference = "1.0.0" diff --git a/tests/testutils/src/lib.rs b/tests/testutils/src/lib.rs new file mode 100644 index 0000000000..9fc85cc24c --- /dev/null +++ b/tests/testutils/src/lib.rs @@ -0,0 +1,64 @@ +extern crate difference; +extern crate file; + +use std::path::{PathBuf, Path}; +use std::fs::read_dir; + +use difference::Changeset; + +pub fn assert_equal_text( + expected: &str, + actual: &str, + path: &Path +) { + if expected != actual { + print_difference(expected, actual, path) + } +} + +pub fn collect_tests(paths: &[&str]) -> Vec { + paths.iter().flat_map(|path| { + let path = test_data_dir().join(path); + test_from_dir(&path).into_iter() + }).collect() +} + +fn test_from_dir(dir: &Path) -> Vec { + let mut acc = Vec::new(); + for file in read_dir(&dir).unwrap() { + let file = file.unwrap(); + let path = file.path(); + if path.extension().unwrap_or_default() == "rs" { + acc.push(path); + } + } + acc.sort(); + acc +} + +fn print_difference(expected: &str, actual: &str, path: &Path) { + let dir = project_dir(); + let path = path.strip_prefix(&dir).unwrap_or_else(|_| path); + println!("\nfile: {}", path.display()); + if expected.trim() == actual.trim() { + println!("whitespace difference"); + println!("rewriting the file"); + file::put_text(path, actual).unwrap(); + } else { + let changeset = Changeset::new(actual, expected, "\n"); + println!("{}", changeset); + } + panic!("Comparison failed") +} + +fn project_dir() -> PathBuf { + let dir = env!("CARGO_MANIFEST_DIR"); + PathBuf::from(dir) + .parent().unwrap() + .parent().unwrap() + .to_owned() +} + +fn test_data_dir() -> PathBuf { + project_dir().join("tests/data") +} \ No newline at end of file