From 130a34b78f3541c15b39666dbf1c75114abbf645 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 09:48:55 -0400 Subject: [PATCH 01/10] Bump to latest tree-sitter-r, includes tree-sitter 0.23.0 --- Cargo.lock | 26 +++++++++++++++++--------- crates/ark/Cargo.toml | 4 ++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19abec488..d6365176a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2218,7 +2218,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", ] [[package]] @@ -2238,7 +2238,7 @@ checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", ] [[package]] @@ -2255,9 +2255,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" @@ -3190,21 +3190,29 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.22.6" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" +checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" dependencies = [ "cc", "regex", + "regex-syntax 0.8.4", + "tree-sitter-language", ] +[[package]] +name = "tree-sitter-language" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" + [[package]] name = "tree-sitter-r" -version = "1.0.1" -source = "git+https://github.com/r-lib/tree-sitter-r?rev=99bf614d9d7e6ac9c7445fa7dc54a590fcdf3ce0#99bf614d9d7e6ac9c7445fa7dc54a590fcdf3ce0" +version = "1.1.0" +source = "git+https://github.com/r-lib/tree-sitter-r?rev=4279b699c47fa87956045980c46c7d30f8c0121b#4279b699c47fa87956045980c46c7d30f8c0121b" dependencies = [ "cc", - "tree-sitter", + "tree-sitter-language", ] [[package]] diff --git a/crates/ark/Cargo.toml b/crates/ark/Cargo.toml index 60852256a..1e8abaf30 100644 --- a/crates/ark/Cargo.toml +++ b/crates/ark/Cargo.toml @@ -47,8 +47,8 @@ serde_json = { version = "1.0.94", features = ["preserve_order"]} stdext = { path = "../stdext" } tokio = { version = "1.26.0", features = ["full"] } tower-lsp = "0.19.0" -tree-sitter = "0.22.6" -tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "99bf614d9d7e6ac9c7445fa7dc54a590fcdf3ce0" } +tree-sitter = "0.23.0" +tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "4279b699c47fa87956045980c46c7d30f8c0121b" } uuid = "1.3.0" url = "2.4.1" walkdir = "2" From 1a09f77a4a9ccfc81fc8b039c63cf7d1c002e940 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 09:49:26 -0400 Subject: [PATCH 02/10] Adapt to new tree-sitter `LANGUAGE` conventions --- crates/ark/src/lsp/documents.rs | 5 +++-- crates/ark/src/lsp/state_handlers.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/ark/src/lsp/documents.rs b/crates/ark/src/lsp/documents.rs index d17e0458f..bfac21a4c 100644 --- a/crates/ark/src/lsp/documents.rs +++ b/crates/ark/src/lsp/documents.rs @@ -64,9 +64,10 @@ impl Document { pub fn new(contents: &str, version: Option) -> Self { // A one-shot parser, assumes the `Document` won't be incrementally reparsed. // Useful for testing, `with_document()`, and `index_file()`. - let language = tree_sitter_r::language(); let mut parser = Parser::new(); - parser.set_language(&language).unwrap(); + parser + .set_language(&tree_sitter_r::LANGUAGE.into()) + .unwrap(); Self::new_with_parser(contents, &mut parser, version) } diff --git a/crates/ark/src/lsp/state_handlers.rs b/crates/ark/src/lsp/state_handlers.rs index d83646b80..6970b40bd 100644 --- a/crates/ark/src/lsp/state_handlers.rs +++ b/crates/ark/src/lsp/state_handlers.rs @@ -165,9 +165,10 @@ pub(crate) fn did_open( let uri = params.text_document.uri; let version = params.text_document.version; - let language = tree_sitter_r::language(); let mut parser = Parser::new(); - parser.set_language(&language).unwrap(); + parser + .set_language(&tree_sitter_r::LANGUAGE.into()) + .unwrap(); let document = Document::new_with_parser(contents, &mut parser, Some(version)); From ab6ea880c92078a4d93c0478ef21a52caa97f47c Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 09:52:41 -0400 Subject: [PATCH 03/10] Update to `LANGUAGE` in tests too --- crates/ark/src/lsp/help_topic.rs | 4 +--- crates/ark/src/lsp/selection_range.rs | 32 +++++++-------------------- crates/ark/src/lsp/statement_range.rs | 10 +++------ crates/ark/src/lsp/traits/node.rs | 4 +--- 4 files changed, 13 insertions(+), 37 deletions(-) diff --git a/crates/ark/src/lsp/help_topic.rs b/crates/ark/src/lsp/help_topic.rs index bcc8a7472..84c89ef39 100644 --- a/crates/ark/src/lsp/help_topic.rs +++ b/crates/ark/src/lsp/help_topic.rs @@ -105,11 +105,9 @@ mod tests { #[test] fn test_locate_help_node() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); // On the RHS diff --git a/crates/ark/src/lsp/selection_range.rs b/crates/ark/src/lsp/selection_range.rs index 356fc6010..6e3fe5e02 100644 --- a/crates/ark/src/lsp/selection_range.rs +++ b/crates/ark/src/lsp/selection_range.rs @@ -139,11 +139,9 @@ mod tests { let (text, point) = point_from_cursor(text); - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); let tree = parser.parse(text, None).unwrap(); @@ -176,11 +174,9 @@ mod tests { let (text, point) = point_from_cursor(text); - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); let tree = parser.parse(text, None).unwrap(); @@ -209,12 +205,10 @@ fn <- function(x, arg) { let (text, point) = point_from_cursor(text); - let language = tree_sitter_r::language(); - // create a parser for this document let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); let tree = parser.parse(text, None).unwrap(); @@ -258,11 +252,9 @@ fn <- function(x, arg) { #[test] #[rustfmt::skip] fn test_selection_range_assignment() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); let text = " @@ -295,11 +287,9 @@ fn <- function() { #[test] #[rustfmt::skip] fn test_selection_range_call_arguments() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .unwrap(); let text = " @@ -337,11 +327,9 @@ fn(@a, b, c) #[test] #[rustfmt::skip] fn test_selection_range_subset_arguments() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .unwrap(); let text = " @@ -384,11 +372,9 @@ x[a, @fn(), c] #[test] #[rustfmt::skip] fn test_selection_range_subset2_arguments() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .unwrap(); let text = " @@ -431,11 +417,9 @@ x[[a, @fn(), c]] #[test] #[rustfmt::skip] fn test_selection_range_namespaced_calls() { - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .unwrap(); let text = " diff --git a/crates/ark/src/lsp/statement_range.rs b/crates/ark/src/lsp/statement_range.rs index 1cf993b6e..2eedb9041 100644 --- a/crates/ark/src/lsp/statement_range.rs +++ b/crates/ark/src/lsp/statement_range.rs @@ -649,11 +649,9 @@ fn test_statement_range() { let x = x.replace("@", ""); let x = x.replace(">>", ""); - let language = tree_sitter_r::language(); - let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("Failed to create parser"); let ast = parser.parse(x, None).unwrap(); @@ -1286,10 +1284,9 @@ test_that('stuff', { "; - let language = tree_sitter_r::language(); let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("Failed to create parser"); let ast = parser.parse(contents, None).unwrap(); let root = ast.root_node(); @@ -1304,10 +1301,9 @@ test_that('stuff', { } "; - let language = tree_sitter_r::language(); let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("Failed to create parser"); let ast = parser.parse(contents, None).unwrap(); let root = ast.root_node(); diff --git a/crates/ark/src/lsp/traits/node.rs b/crates/ark/src/lsp/traits/node.rs index df3fc1696..f32744aa0 100644 --- a/crates/ark/src/lsp/traits/node.rs +++ b/crates/ark/src/lsp/traits/node.rs @@ -287,12 +287,10 @@ fn <- function(x, arg) { let (text, point) = point_from_cursor(text); - let language = tree_sitter_r::language(); - // create a parser for this document let mut parser = Parser::new(); parser - .set_language(&language) + .set_language(&tree_sitter_r::LANGUAGE.into()) .expect("failed to create parser"); let tree = parser.parse(text, None).unwrap(); From 7b2bc6dc29c3fb163243fa12091dd1cda119cae0 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 09:53:05 -0400 Subject: [PATCH 04/10] Add test for previous `(0, 0)` `program` node issue --- crates/ark/src/lsp/documents.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/ark/src/lsp/documents.rs b/crates/ark/src/lsp/documents.rs index bfac21a4c..98a2a7be6 100644 --- a/crates/ark/src/lsp/documents.rs +++ b/crates/ark/src/lsp/documents.rs @@ -225,4 +225,11 @@ mod tests { let point = compute_point(Point::new(0, 0), "abcdefghi\n"); assert_eq!(point, Point::new(1, 0)); } + + #[test] + fn test_document_starts_at_0_0_with_leading_whitespace() { + let document = Document::new("\n\n# hi there", None); + let root = document.ast.root_node(); + assert_eq!(root.start_position(), Point::new(0, 0)); + } } From 49fc325f24e35ba271a1bfce628f4fa8ba4e8e61 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 09:58:08 -0400 Subject: [PATCH 05/10] Fix more invalid R syntax in `indent.R` Otherwise example 34 now indents out one level --- crates/ark/src/lsp/snapshots/indent.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ark/src/lsp/snapshots/indent.R b/crates/ark/src/lsp/snapshots/indent.R index c2a591aeb..46f144b57 100644 --- a/crates/ark/src/lsp/snapshots/indent.R +++ b/crates/ark/src/lsp/snapshots/indent.R @@ -1098,11 +1098,11 @@ fun_call(object1 + object2 ~ object3 + argument) ## 32 -fun_call(object ~ +fun_call(~ object ) ## 33 -fun_call(object + +fun_call(object + object2 ) ## 34 From f09ce90210815dbc890937f80bec100444bdfbdf Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 11:51:55 -0400 Subject: [PATCH 06/10] Custom panic message in `statement_range_test()` --- crates/ark/src/lsp/statement_range.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ark/src/lsp/statement_range.rs b/crates/ark/src/lsp/statement_range.rs index 2eedb9041..883a44461 100644 --- a/crates/ark/src/lsp/statement_range.rs +++ b/crates/ark/src/lsp/statement_range.rs @@ -509,6 +509,8 @@ fn test_statement_range() { // by tree-sitter. It is generally best to left align the string against the // far left margin to avoid unexpected whitespace and mimic real life. fn statement_range_test(x: &str) { + let original = x; + let lines = x.split("\n").collect::>(); let mut cursor: Option = None; @@ -660,8 +662,8 @@ fn test_statement_range() { let node = find_statement_range_node(&root, cursor.unwrap().row).unwrap(); - assert_eq!(node.start_position(), sel_start.unwrap()); - assert_eq!(node.end_position(), sel_end.unwrap()); + assert_eq!(node.start_position(), sel_start.unwrap(), "Failed on test {original}"); + assert_eq!(node.end_position(), sel_end.unwrap(), "Failed on test {original}"); } // Simple test From c2907d471463f565dac43f9e1d979e8b769af667 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 11:52:22 -0400 Subject: [PATCH 07/10] Bump tree-sitter-r one more time for https://github.com/r-lib/tree-sitter-r/pull/145 --- Cargo.lock | 2 +- crates/ark/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6365176a..741e79002 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3209,7 +3209,7 @@ checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" [[package]] name = "tree-sitter-r" version = "1.1.0" -source = "git+https://github.com/r-lib/tree-sitter-r?rev=4279b699c47fa87956045980c46c7d30f8c0121b#4279b699c47fa87956045980c46c7d30f8c0121b" +source = "git+https://github.com/r-lib/tree-sitter-r?rev=f0679b90d0e154e21d7219f7ff0ec9683a39f97b#f0679b90d0e154e21d7219f7ff0ec9683a39f97b" dependencies = [ "cc", "tree-sitter-language", diff --git a/crates/ark/Cargo.toml b/crates/ark/Cargo.toml index 1e8abaf30..4b9b4cd41 100644 --- a/crates/ark/Cargo.toml +++ b/crates/ark/Cargo.toml @@ -48,7 +48,7 @@ stdext = { path = "../stdext" } tokio = { version = "1.26.0", features = ["full"] } tower-lsp = "0.19.0" tree-sitter = "0.23.0" -tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "4279b699c47fa87956045980c46c7d30f8c0121b" } +tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "f0679b90d0e154e21d7219f7ff0ec9683a39f97b" } uuid = "1.3.0" url = "2.4.1" walkdir = "2" From ff95d917570f64fa0dbe2ea4afb5c1b16ee49037 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 12:06:44 -0400 Subject: [PATCH 08/10] Fix tests related to `(0, 0)` `program` node issue --- crates/ark/src/lsp/selection_range.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ark/src/lsp/selection_range.rs b/crates/ark/src/lsp/selection_range.rs index 6e3fe5e02..641fc769e 100644 --- a/crates/ark/src/lsp/selection_range.rs +++ b/crates/ark/src/lsp/selection_range.rs @@ -156,7 +156,7 @@ mod tests { assert_eq!(selection.range.end_point, Point::new(3, 1)); let selection = selection.parent.as_ref().unwrap(); - assert_eq!(selection.range.start_point, Point::new(1, 0)); + assert_eq!(selection.range.start_point, Point::new(0, 0)); assert_eq!(selection.range.end_point, Point::new(6, 0)); assert!(selection.parent.is_none()); } @@ -187,7 +187,7 @@ mod tests { // Just 1 selection, the whole document let selection = selections.get(0).unwrap(); - assert_eq!(selection.range.start_point, Point::new(1, 0)); + assert_eq!(selection.range.start_point, Point::new(0, 0)); assert_eq!(selection.range.end_point, Point::new(6, 0)); assert!(selection.parent.is_none()); } @@ -244,7 +244,7 @@ fn <- function(x, arg) { // Whole document let selection = selection.parent.as_ref().unwrap(); - assert_eq!(selection.range.start_point, Point::new(1, 0)); + assert_eq!(selection.range.start_point, Point::new(0, 0)); assert_eq!(selection.range.end_point, Point::new(6, 0)); assert!(selection.parent.is_none()); } From 119e1656cd87a0e508e6639889a1d81b3940d341 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 12:07:50 -0400 Subject: [PATCH 09/10] Adapt diagnostics tests to tree-sitter-r grammar changes --- crates/ark/src/lsp/diagnostics_syntactic.rs | 35 +++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/ark/src/lsp/diagnostics_syntactic.rs b/crates/ark/src/lsp/diagnostics_syntactic.rs index 934bb8455..73ed92a4d 100644 --- a/crates/ark/src/lsp/diagnostics_syntactic.rs +++ b/crates/ark/src/lsp/diagnostics_syntactic.rs @@ -478,12 +478,17 @@ identity(1) #[test] fn test_error_precision() { + // The actual error is up to tree-sitter-r's error recovery, + // but it should always be decent let diagnostics = text_diagnostics("sum(1 * 2 + )"); assert_eq!(diagnostics.len(), 1); let diagnostic = diagnostics.get(0).unwrap(); - assert!(diagnostic.message.starts_with("Syntax error")); - assert_eq!(diagnostic.range.start, Position::new(0, 10)); - assert_eq!(diagnostic.range.end, Position::new(0, 11)); + assert_eq!( + diagnostic.message, + "Unmatched closing token. Missing an opening '('." + ); + assert_eq!(diagnostic.range.start, Position::new(0, 12)); + assert_eq!(diagnostic.range.end, Position::new(0, 13)); } #[test] @@ -522,6 +527,8 @@ identity(1) #[test] fn test_unmatched_binary_operator() { + // The actual error is up to tree-sitter-r's error recovery, + // but it should always be decent let text = " { 1 + @@ -533,24 +540,32 @@ identity(1) let diagnostic = diagnostics.get(0).unwrap(); assert_eq!( diagnostic.message, - String::from("Invalid binary operator '+'. Missing a right hand side.") + String::from("Unmatched closing token. Missing an opening '{'.") ); - assert_eq!(diagnostic.range.start, Position::new(2, 3)); - assert_eq!(diagnostic.range.end, Position::new(2, 4)); + assert_eq!(diagnostic.range.start, Position::new(3, 0)); + assert_eq!(diagnostic.range.end, Position::new(3, 1)); } #[test] fn test_unmatched_function_parameters_parentheses() { + // Exact set of diagnostics are up to tree-sitter-r's error recovery, + // but they should be decent at pointing you to the right place let text = " function(x { + 1 + 1 }"; let diagnostics = text_diagnostics(text); - assert_eq!(diagnostics.len(), 1); + assert_eq!(diagnostics.len(), 2); let diagnostic = diagnostics.get(0).unwrap(); - assert!(diagnostic.message.starts_with("Unmatched opening token")); - assert_eq!(diagnostic.range.start, Position::new(1, 8)); - assert_eq!(diagnostic.range.end, Position::new(1, 9)); + assert!(diagnostic.message.starts_with("Syntax error")); + assert_eq!(diagnostic.range.start, Position::new(1, 11)); + assert_eq!(diagnostic.range.end, Position::new(1, 12)); + + let diagnostic = diagnostics.get(1).unwrap(); + assert!(diagnostic.message.starts_with("Unmatched closing token")); + assert_eq!(diagnostic.range.start, Position::new(3, 0)); + assert_eq!(diagnostic.range.end, Position::new(3, 1)); } } From 0952232a6ab1bff8ca78b1226d05a99d9c2cad7e Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Tue, 17 Sep 2024 12:09:01 -0400 Subject: [PATCH 10/10] Bump tree-sitter-r once again --- Cargo.lock | 2 +- crates/ark/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 741e79002..02641c992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3209,7 +3209,7 @@ checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" [[package]] name = "tree-sitter-r" version = "1.1.0" -source = "git+https://github.com/r-lib/tree-sitter-r?rev=f0679b90d0e154e21d7219f7ff0ec9683a39f97b#f0679b90d0e154e21d7219f7ff0ec9683a39f97b" +source = "git+https://github.com/r-lib/tree-sitter-r?rev=2097fa502efa21349d26af0ffee55d773015e481#2097fa502efa21349d26af0ffee55d773015e481" dependencies = [ "cc", "tree-sitter-language", diff --git a/crates/ark/Cargo.toml b/crates/ark/Cargo.toml index 4b9b4cd41..5f2ad0094 100644 --- a/crates/ark/Cargo.toml +++ b/crates/ark/Cargo.toml @@ -48,7 +48,7 @@ stdext = { path = "../stdext" } tokio = { version = "1.26.0", features = ["full"] } tower-lsp = "0.19.0" tree-sitter = "0.23.0" -tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "f0679b90d0e154e21d7219f7ff0ec9683a39f97b" } +tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "2097fa502efa21349d26af0ffee55d773015e481" } uuid = "1.3.0" url = "2.4.1" walkdir = "2"