From f03b305bb73d7457eb6974cdadf3932b7c57cc9e Mon Sep 17 00:00:00 2001 From: snobee <86692451+snobee@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:25:03 -0800 Subject: [PATCH] Add tests for automatic annotation --- crates/cli/src/format.rs | 72 ++++++++++- crates/language_server/src/server.rs | 183 +++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/format.rs b/crates/cli/src/format.rs index e4c3daa6455..48283aea8bc 100644 --- a/crates/cli/src/format.rs +++ b/crates/cli/src/format.rs @@ -415,7 +415,8 @@ pub fn annotation_edit( #[cfg(test)] mod tests { use super::*; - use std::fs::File; + use indoc::indoc; + use std::fs::{read_to_string, File}; use std::io::Write; use tempfile::{tempdir, TempDir}; @@ -515,4 +516,73 @@ main = cleanup_temp_dir(dir); } + + const HEADER: &str = indoc! {r#" + app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } + + "#}; + + fn annotate_string(before: String) -> String { + let dir = tempdir().unwrap(); + let file_path = setup_test_file(dir.path(), "before.roc", &before); + + let arena = Bump::new(); + let result = annotate_file(&arena, file_path.clone()); + result.unwrap(); + + let annotated = read_to_string(file_path).unwrap(); + + cleanup_temp_dir(dir); + annotated + } + + #[test] + fn test_annotate_simple() { + let before = HEADER.to_string() + + indoc! {r#" + main = + Task.ok {}"#}; + + let after = HEADER.to_string() + + indoc! {r#" + main : Task {} * + main = + Task.ok {} + "#}; + + let annotated = annotate_string(before); + + assert_eq!(annotated, after); + } + + #[test] + fn test_annotate_empty() { + let before = HEADER.to_string(); + let after = HEADER.to_string() + "\n"; + let annotated = annotate_string(before); + + assert_eq!(annotated, after); + } + + #[test] + fn test_annotate_destructure() { + let before = HEADER.to_string() + + indoc! {r#" + {a, b} = {a: Task.ok {}, b: (1, 2)} + + main = a"#}; + + let after = HEADER.to_string() + + indoc! {r#" + {a, b} : { a : Task {} *, b : ( Num *, Num * )* } + {a, b} = {a: Task.ok {}, b: (1, 2)} + + main : Task {} * + main = a + "#}; + + let annotated = annotate_string(before); + + assert_eq!(annotated, after); + } } diff --git a/crates/language_server/src/server.rs b/crates/language_server/src/server.rs index 7c71bd452da..8cda414d35b 100644 --- a/crates/language_server/src/server.rs +++ b/crates/language_server/src/server.rs @@ -626,4 +626,187 @@ mod tests { "#]] .assert_debug_eq(&actual); } + + const HEADER: &str = indoc! {r#" + app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } + + "#}; + + async fn code_action_edits(doc: String, position: Position, name: &str) -> Vec { + let (inner, url) = test_setup(doc.clone()).await; + let registry = &inner.registry; + + let actions = registry + .code_actions(&url, Range::new(position, position)) + .await + .unwrap(); + + actions + .into_iter() + .find_map(|either| match either { + CodeActionOrCommand::CodeAction(action) if name == action.title => Some(action), + _ => None, + }) + .expect("Code action not present") + .edit + .expect("Code action does not have an associated edit") + .changes + .expect("Edit does not have any changes") + .get(&url) + .expect("Edit does not have changes for this file") + .clone() + } + + #[tokio::test] + async fn test_annotate_single() { + let edit = code_action_edits( + HEADER.to_string() + "main = Task.ok {}", + Position::new(2, 2), + "Add signature", + ) + .await; + + expect![[r#" + [ + TextEdit { + range: Range { + start: Position { + line: 2, + character: 0, + }, + end: Position { + line: 2, + character: 0, + }, + }, + new_text: "main : Task {} *\n", + }, + ] + "#]] + .assert_debug_eq(&edit); + } + + #[tokio::test] + async fn test_annotate_top_level() { + let edit = code_action_edits( + HEADER.to_string() + + indoc! {r#" + other = \_ -> + Task.ok {} + + main = + other {} + "#}, + Position::new(4, 0), + "Add top-level signatures", + ) + .await; + + expect![[r#" + [ + TextEdit { + range: Range { + start: Position { + line: 2, + character: 0, + }, + end: Position { + line: 2, + character: 0, + }, + }, + new_text: "other : * -> Task {} *\n", + }, + TextEdit { + range: Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + new_text: "main : Task {} *\n", + }, + ] + "#]] + .assert_debug_eq(&edit); + } + + #[tokio::test] + async fn test_annotate_inner() { + let edit = code_action_edits( + HEADER.to_string() + + indoc! {r#" + main = + start = 10 + fib start 0 1 + + fib = \n, a, b -> + if n == 0 then + a + else + fib (n - 1) b (a + b) + "#}, + Position::new(3, 8), + "Add signature", + ) + .await; + + expect![[r#" + [ + TextEdit { + range: Range { + start: Position { + line: 3, + character: 0, + }, + end: Position { + line: 3, + character: 0, + }, + }, + new_text: " start : Num *\n", + }, + ] + "#]] + .assert_debug_eq(&edit); + } + + #[tokio::test] + async fn test_annotate_inner_effectful() { + let edit = code_action_edits( + HEADER.to_string() + + indoc! {r#" + main = + Stdout.line! "What's your name?" + name = Stdin.line! + Stdout.line! "Hi $(name)!" + "#}, + Position::new(4, 6), + "Add signature", + ) + .await; + + expect![[r#" + [ + TextEdit { + range: Range { + start: Position { + line: 4, + character: 0, + }, + end: Position { + line: 4, + character: 0, + }, + }, + new_text: " name : Str\n", + }, + ] + "#]] + .assert_debug_eq(&edit); + } }