Skip to content

Commit

Permalink
feat: Add validation for nushell
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelarie committed Oct 10, 2024
1 parent cec13ab commit a3999af
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 38 deletions.
10 changes: 7 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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!();
}
}
93 changes: 58 additions & 35 deletions src/syntax_tree/alias.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -34,6 +32,46 @@ enum AliasError {
InvalidUtf8Text,
}

fn extract_alias_name(
cursor: &mut tree_sitter::TreeCursor,
source: &[u8],
) -> Result<String, AliasError> {
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],
Expand Down Expand Up @@ -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();

Expand All @@ -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<Alias> {
let mut aliases = Vec::new();

// Skip first node (program)
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/syntax_tree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod alias;
pub mod printer;
pub mod traverser;
pub mod nushell;

#[allow(unused)]
pub use printer::print_tree;
#[allow(unused)]
pub use traverser::traverse_tree;

pub use alias::find_aliases;
pub use nushell::validate_nu_language;
13 changes: 13 additions & 0 deletions src/syntax_tree/nushell.rs
Original file line number Diff line number Diff line change
@@ -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()
}
1 change: 1 addition & 0 deletions src/test/examples/bash_aliases
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit a3999af

Please sign in to comment.