From a3999af82a8e54d0753d6295af96d0b80d10a50f Mon Sep 17 00:00:00 2001 From: marcelarie Date: Thu, 10 Oct 2024 18:25:26 +0200 Subject: [PATCH] feat: Add validation for nushell --- src/main.rs | 10 ++-- src/syntax_tree/alias.rs | 93 +++++++++++++++++++++------------- src/syntax_tree/mod.rs | 2 + src/syntax_tree/nushell.rs | 13 +++++ src/test/examples/bash_aliases | 1 + 5 files changed, 81 insertions(+), 38 deletions(-) create mode 100644 src/syntax_tree/nushell.rs diff --git a/src/main.rs b/src/main.rs index 68b87e8..6ed5aed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use syntax_tree::find_aliases; use tree_sitter::Parser; fn main() { - const TEST_FILE_PATH: &str = "./src/test/examples/special_chars_bash_alias"; + const TEST_FILE_PATH: &str = "./src/test/examples/bash_aliases"; let code = fs::read_to_string(TEST_FILE_PATH).expect("Error reading file"); let mut parser = Parser::new(); @@ -20,7 +20,11 @@ fn main() { let mut cursor = tree.walk(); let aliases = find_aliases(&mut cursor, code.as_bytes()); - for (name, command) in aliases { - println!("{} => {}", name, command); + for alias in aliases { + println!(); + println!("Alias name: {}", alias.name); + println!("Alias content: {}", alias.content); + println!("Valid nushell: {}", alias.is_valid_nushell); + println!(); } } diff --git a/src/syntax_tree/alias.rs b/src/syntax_tree/alias.rs index 14847cd..23fc72a 100644 --- a/src/syntax_tree/alias.rs +++ b/src/syntax_tree/alias.rs @@ -1,6 +1,4 @@ -use crate::syntax_tree::printer; - -use super::print_tree; +use super::validate_nu_language; /// Unquote a string (remove the quotes if it has) // https://www.gnu.org/software/bash/manual/html_node/Quoting.html @@ -34,6 +32,46 @@ enum AliasError { InvalidUtf8Text, } +fn extract_alias_name( + cursor: &mut tree_sitter::TreeCursor, + source: &[u8], +) -> Result { + match cursor.node().kind() { + // This happens in cases like this: + // alias ll='ls -l' + "word" => match cursor.node().utf8_text(source) { + Ok(alias_content) => { + Ok(alias_content.trim_end_matches('=').to_string()) + } + Err(_) => Err(AliasError::InvalidUtf8Text), + }, + // This happens in cases like this: + // alias "abc!"='echo String with special characters' + "string" => { + if !cursor.goto_first_child() { + return Err(AliasError::MissingAliasName); + } + if !cursor.goto_next_sibling() { + return Err(AliasError::MissingAliasName); + } + + let string_node = cursor.node(); + + cursor.goto_parent(); + + if !cursor.goto_next_sibling() { + return Err(AliasError::MissingAliasName); + } + + match string_node.utf8_text(source) { + Ok(alias_content) => Ok(alias_content.to_string()), + Err(_) => Err(AliasError::InvalidUtf8Text), + } + } + _ => Err(AliasError::MissingCommandName), + } +} + fn extract_alias( node: tree_sitter::Node, source: &[u8], @@ -61,37 +99,7 @@ fn extract_alias( cursor.goto_first_child(); - let alias_name = match cursor.node().kind() { - "string" => { - if !cursor.goto_first_child() { - return Err(AliasError::MissingAliasName); - } - if !cursor.goto_next_sibling() { - return Err(AliasError::MissingAliasName); - } - - let string_node = cursor.node(); - - cursor.goto_parent(); - cursor.goto_next_sibling(); - - // Attempt to extract the text content from the string node - match string_node.utf8_text(source) { - Ok(alias_content) => Ok(alias_content.to_string()), - Err(_) => Err(AliasError::InvalidUtf8Text), - } - } - "word" => { - // Extract the alias name from the word node and trim '=' at the end - match cursor.node().utf8_text(source) { - Ok(alias_content) => { - Ok(alias_content.trim_end_matches('=').to_string()) - } - Err(_) => Err(AliasError::InvalidUtf8Text), - } - } - _ => Err(AliasError::MissingCommandName), - }?; + let alias_name = extract_alias_name(&mut cursor, source)?; cursor.goto_next_sibling(); @@ -107,10 +115,17 @@ fn extract_alias( Ok((alias_name, unquoted_alias_content)) } +#[derive(Debug, Clone)] +pub struct Alias { + pub name: String, + pub content: String, + pub is_valid_nushell: bool, +} + pub fn find_aliases( cursor: &mut tree_sitter::TreeCursor, source: &[u8], -) -> Vec<(String, String)> { +) -> Vec { let mut aliases = Vec::new(); // Skip first node (program) @@ -123,6 +138,14 @@ pub fn find_aliases( if node.kind() == "command" { if let Ok(alias) = extract_alias(node, source) { + let (name, content) = alias; + let is_valid_nushell = validate_nu_language(&content); + + let alias = Alias { + name, + content, + is_valid_nushell, + }; aliases.push(alias); } } // TODO: Implement alias detection inside functions diff --git a/src/syntax_tree/mod.rs b/src/syntax_tree/mod.rs index b11bb7f..582882d 100644 --- a/src/syntax_tree/mod.rs +++ b/src/syntax_tree/mod.rs @@ -1,6 +1,7 @@ pub mod alias; pub mod printer; pub mod traverser; +pub mod nushell; #[allow(unused)] pub use printer::print_tree; @@ -8,3 +9,4 @@ pub use printer::print_tree; pub use traverser::traverse_tree; pub use alias::find_aliases; +pub use nushell::validate_nu_language; diff --git a/src/syntax_tree/nushell.rs b/src/syntax_tree/nushell.rs new file mode 100644 index 0000000..c7e4377 --- /dev/null +++ b/src/syntax_tree/nushell.rs @@ -0,0 +1,13 @@ +use tree_sitter::Parser; +use tree_sitter_nu::LANGUAGE; + +pub fn validate_nu_language(content: &String) -> bool { + let mut parser = Parser::new(); + let nu_lang = LANGUAGE.into(); + + parser + .set_language(&nu_lang) + .expect("Error loading Nu parser"); + + parser.parse(content, None).is_some() +} diff --git a/src/test/examples/bash_aliases b/src/test/examples/bash_aliases index 4faf767..f5c68c0 100644 --- a/src/test/examples/bash_aliases +++ b/src/test/examples/bash_aliases @@ -8,6 +8,7 @@ alias ll='ls -l' alias "abc!"='echo String with special characters' alias la= 'ls -A' # This alias is invalid and should be ignored alias gitlog='git log --graph --oneline --decorate --all' +alias invalid_nushell_alias='echo $HOME' # Cases like this are not handled yet by the Parser my_function() {