Skip to main content

tree_sitter_perl_next/
lib.rs

1//! Perl language support for the [tree-sitter](https://tree-sitter.github.io/) parsing library.
2
3use tree_sitter_language::LanguageFn;
4
5unsafe extern "C" {
6    fn tree_sitter_perl() -> *const ();
7}
8
9/// The tree-sitter [`LanguageFn`] for the Perl grammar.
10pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_perl) };
11
12/// The content of the [`node-types.json`] file for the Perl grammar.
13pub const NODE_TYPES: &str = include_str!("node-types.json");
14
15/// The syntax highlighting query for the Perl grammar.
16pub const HIGHLIGHTS_QUERY: &str = include_str!("../queries/highlights.scm");
17
18/// The injections query for Perl.
19pub const INJECTIONS_QUERY: &str = include_str!("../queries/injections.scm");
20
21#[cfg(test)]
22mod tests {
23    #[test]
24    fn test_can_load_grammar() {
25        let mut parser = tree_sitter::Parser::new();
26        parser
27            .set_language(&super::LANGUAGE.into())
28            .expect("Error loading Perl parser");
29    }
30
31    #[test]
32    fn test_can_parse_perl() {
33        let mut parser = tree_sitter::Parser::new();
34        parser
35            .set_language(&super::LANGUAGE.into())
36            .expect("Error loading Perl parser");
37
38        let code = r#"
39#!/usr/bin/perl
40use strict;
41use warnings;
42
43my $name = "World";
44my @items = (1, 2, 3);
45my %lookup = (a => 1, b => 2);
46
47sub greet {
48    my ($who) = @_;
49    print "Hello, $who!\n";
50}
51
52greet($name);
53
54for my $item (@items) {
55    print "$item\n";
56}
57"#;
58
59        let tree = parser.parse(code, None).unwrap();
60        let root = tree.root_node();
61        assert!(
62            !root.has_error(),
63            "Parse tree has errors: {}",
64            root.to_sexp()
65        );
66    }
67
68    #[test]
69    fn test_highlights_query_is_valid() {
70        let language: tree_sitter::Language = super::LANGUAGE.into();
71        tree_sitter::Query::new(&language, super::HIGHLIGHTS_QUERY)
72            .expect("HIGHLIGHTS_QUERY should be a valid query for the Perl grammar");
73    }
74
75    #[test]
76    fn test_highlights_query_matches_perl_nodes() {
77        use tree_sitter::{Parser, Query, QueryCursor, StreamingIterator as _};
78
79        let language: tree_sitter::Language = super::LANGUAGE.into();
80        let mut parser = Parser::new();
81        parser.set_language(&language).unwrap();
82
83        let code = r#"my $x = "hello";"#;
84        let tree = parser.parse(code, None).unwrap();
85
86        let query = Query::new(&language, super::HIGHLIGHTS_QUERY).unwrap();
87        let mut cursor = QueryCursor::new();
88        let mut matches = cursor.matches(&query, tree.root_node(), code.as_bytes());
89
90        let mut capture_names: Vec<String> = vec![];
91        while let Some(m) = matches.next() {
92            for cap in m.captures {
93                let name = query.capture_names()[cap.index as usize].to_string();
94                if !capture_names.contains(&name) {
95                    capture_names.push(name);
96                }
97            }
98        }
99
100        assert!(
101            capture_names.contains(&"keyword".to_string()),
102            "should match 'my' as @keyword"
103        );
104        assert!(
105            capture_names.contains(&"variable".to_string()),
106            "should match $x as @variable"
107        );
108        assert!(
109            capture_names.contains(&"string".to_string()),
110            "should match string literal as @string"
111        );
112    }
113
114    #[test]
115    fn test_injections_query_is_valid() {
116        let language: tree_sitter::Language = super::LANGUAGE.into();
117        tree_sitter::Query::new(&language, super::INJECTIONS_QUERY)
118            .expect("INJECTIONS_QUERY should be a valid query for the Perl grammar");
119    }
120
121    #[test]
122    fn test_injections_query_matches_substitution_with_e_modifier() {
123        use tree_sitter::{Parser, Query, QueryCursor, StreamingIterator as _};
124
125        let language: tree_sitter::Language = super::LANGUAGE.into();
126        let mut parser = Parser::new();
127        parser.set_language(&language).unwrap();
128
129        let code = r#"$x =~ s/foo/uc($1)/e;"#;
130        let tree = parser.parse(code, None).unwrap();
131
132        let query = Query::new(&language, super::INJECTIONS_QUERY).unwrap();
133        let mut cursor = QueryCursor::new();
134        let mut matches = cursor.matches(&query, tree.root_node(), code.as_bytes());
135
136        let mut found_perl_injection = false;
137        while let Some(m) = matches.next() {
138            for prop in query.property_settings(m.pattern_index) {
139                if prop.key.as_ref() == "injection.language"
140                    && let Some(val) = &prop.value
141                    && val.as_ref() == "perl"
142                {
143                    found_perl_injection = true;
144                }
145            }
146        }
147
148        assert!(
149            found_perl_injection,
150            "should inject Perl for s///e replacement"
151        );
152    }
153}