use dever::compiler::format; use dever::syntax::source::Source; use std::path::PathBuf; #[test] fn format_keeps_literals_comments_groups_and_field_order() { let text = "# leading\nmain( ) (answer: Int) {\n answer=(1+2)*3 # same line\n}\n"; let source = Source::new(0, "case.dever", text.into()); let output = format::format(&source).unwrap(); assert_eq!( output, "# leading\nmain() (answer: Int) {\n answer = (1 + 2) * 3 # same line\n}\n" ); assert_eq!( format::format(&Source::new(0, "case.dever", output.clone())).unwrap(), output ); let literals = Source::new( 0, "case.dever", "main() (answer: Text) { answer=\"# \\u{41} \\\"\" }\n".into(), ); assert!( format::format(&literals) .unwrap() .contains("\"# \\u{41} \\\"\"") ); let fields = Source::new( 0, "case.dever", "main() (answer: Item) { answer=Item { second=2, first=1 } }\n".into(), ); assert!( format::format(&fields) .unwrap() .contains("second = 2, first = 1") ); } #[test] fn format_preserves_crlf() { let text = "main( ) (answer: Int) {\r\n answer=1\r\n}\r\n"; let output = format::format(&Source::new(0, "case.dever", text.into())).unwrap(); assert_eq!( output, text.replace("main( )", "main()") .replace(" answer=1", " answer = 1") ); } #[test] fn format_preserves_local_type_annotations() { let source = Source::new( 0, "case.dever", "main() () { value:List=[null,1] }".into(), ); let formatted = format::format(&source).unwrap(); assert!( formatted.contains("value: List = [null, 1]"), "{formatted}" ); assert_eq!( format::format(&Source::new(0, "case.dever", formatted.clone())).unwrap(), formatted ); } #[test] fn format_preserves_text_policy_and_camel_functions() { let input = "type Category { name: Text(1,80)\ncode: Text lower unique\nbody: Text raw\nnote: Text? upper }\nreadName(input_value: Text) (output_value: Text) { output_value = input_value }"; let output = format::format(&Source::new(0, "model.dever", input.into())).unwrap(); for preserved in [ "code: Text lower unique", "body: Text raw", "note: Text? upper", "readName(input_value: Text)", ] { assert!(output.contains(preserved), "{output}"); } assert_eq!( format::format(&Source::new(0, "model.dever", output.clone())).unwrap(), output ); } struct Directory(PathBuf); impl Directory { fn new() -> Self { let path = std::env::temp_dir().join(format!( "dever-format-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() )); std::fs::create_dir(&path).unwrap(); Self(path) } } impl Drop for Directory { fn drop(&mut self) { let _ = std::fs::remove_dir_all(&self.0); } } #[test] fn project_prepares_module_and_tests_before_any_rewrite() { let directory = Directory::new(); std::fs::create_dir(directory.0.join("module")).unwrap(); std::fs::create_dir(directory.0.join("test")).unwrap(); let module = directory.0.join("module/main.dever"); let test = directory.0.join("test/main.dever"); let input = "main( ) () {}\n"; std::fs::write(&module, input).unwrap(); std::fs::write(&test, "main( {").unwrap(); assert!(format::project(&directory.0, false).is_err()); assert_eq!(std::fs::read_to_string(&module).unwrap(), input); std::fs::write(&test, input).unwrap(); let report = format::project(&directory.0, true).unwrap(); assert_eq!(report.checked, 2); assert_eq!(report.changed.len(), 2); assert_eq!(std::fs::read_to_string(&module).unwrap(), input); format::project(&directory.0, false).unwrap(); assert!( format::project(&directory.0, true) .unwrap() .changed .is_empty() ); } #[test] #[cfg(unix)] fn project_rejects_links_without_touching_their_target() { let directory = Directory::new(); let target = directory.0.join("actual.dever"); std::fs::write(&target, "main( ) () {}\n").unwrap(); std::os::unix::fs::symlink(&target, directory.0.join("link.dever")).unwrap(); assert!( format::project(&directory.0, false) .unwrap_err() .contains("links") ); assert_eq!(std::fs::read_to_string(target).unwrap(), "main( ) () {}\n"); }