zift 0.2.3

Scan codebases for embedded authorization logic and generate Policy as Code (Rego for OPA, Cedar for AWS Verified Permissions and other Cedar-compatible engines)
Documentation
use tree_sitter::Tree;

use crate::error::{Result, ZiftError};
use crate::types::Language;

/// Returns true if the language has tree-sitter parser support.
pub fn is_language_supported(lang: Language) -> bool {
    get_language(lang, false).is_ok()
}

pub fn get_language(lang: Language, is_tsx_jsx: bool) -> Result<tree_sitter::Language> {
    match (lang, is_tsx_jsx) {
        (Language::TypeScript, false) => Ok(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
        (Language::TypeScript, true) => Ok(tree_sitter_typescript::LANGUAGE_TSX.into()),
        (Language::JavaScript, _) => Ok(tree_sitter_javascript::LANGUAGE.into()),
        (Language::Java, _) => Ok(tree_sitter_java::LANGUAGE.into()),
        (Language::Python, _) => Ok(tree_sitter_python::LANGUAGE.into()),
        (Language::Go, _) => Ok(tree_sitter_go::LANGUAGE.into()),
        (Language::CSharp, _) => Ok(tree_sitter_c_sharp::LANGUAGE.into()),
        (Language::Kotlin, _) => Ok(tree_sitter_kotlin_ng::LANGUAGE.into()),
        (Language::Ruby, _) => Ok(tree_sitter_ruby::LANGUAGE.into()),
        (Language::Php, _) => Ok(tree_sitter_php::LANGUAGE_PHP.into()),
    }
}

pub fn parse_source(
    parser: &mut tree_sitter::Parser,
    source: &[u8],
    lang: Language,
    is_tsx_jsx: bool,
) -> Result<Tree> {
    let ts_lang = get_language(lang, is_tsx_jsx)?;
    parser
        .set_language(&ts_lang)
        .map_err(|e| ZiftError::General(format!("failed to set parser language: {e}")))?;

    parser
        .parse(source, None)
        .ok_or_else(|| ZiftError::General("tree-sitter parse returned None".into()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_typescript() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"const x: number = 42;";
        let tree = parse_source(&mut parser, source, Language::TypeScript, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn parse_tsx() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"const App = () => <div>Hello</div>;";
        let tree = parse_source(&mut parser, source, Language::TypeScript, true).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn parse_javascript() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"const x = 42;";
        let tree = parse_source(&mut parser, source, Language::JavaScript, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn parse_java() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"public class Foo { public void bar() {} }";
        let tree = parse_source(&mut parser, source, Language::Java, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn java_is_supported() {
        assert!(is_language_supported(Language::Java));
    }

    #[test]
    fn parse_python() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"def is_admin(user):\n    return user.role == 'admin'\n";
        let tree = parse_source(&mut parser, source, Language::Python, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn python_is_supported() {
        assert!(is_language_supported(Language::Python));
    }

    #[test]
    fn parse_go() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"package main\n\nfunc main() {\n\tprintln(\"hi\")\n}\n";
        let tree = parse_source(&mut parser, source, Language::Go, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn go_is_supported() {
        assert!(is_language_supported(Language::Go));
    }

    #[test]
    fn parse_csharp() {
        let mut parser = tree_sitter::Parser::new();
        let source = br#"
using Microsoft.AspNetCore.Authorization;

[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase {
    public IActionResult Delete() => Ok();
}
"#;
        let tree = parse_source(&mut parser, source, Language::CSharp, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn csharp_is_supported() {
        assert!(is_language_supported(Language::CSharp));
    }

    #[test]
    fn parse_kotlin() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"class Foo {\n    fun bar() {}\n}\n";
        let tree = parse_source(&mut parser, source, Language::Kotlin, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn kotlin_is_supported() {
        assert!(is_language_supported(Language::Kotlin));
    }

    #[test]
    fn parse_ruby() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"class Foo\n  def bar\n  end\nend\n";
        let tree = parse_source(&mut parser, source, Language::Ruby, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn ruby_is_supported() {
        assert!(is_language_supported(Language::Ruby));
    }

    #[test]
    fn parse_php() {
        let mut parser = tree_sitter::Parser::new();
        let source = b"<?php\nclass Foo { public function bar() {} }\n";
        let tree = parse_source(&mut parser, source, Language::Php, false).unwrap();
        assert!(!tree.root_node().has_error());
    }

    #[test]
    fn php_is_supported() {
        assert!(is_language_supported(Language::Php));
    }
}