fork of https://github.com/tree-sitter/tree-sitter-graph
1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8use indoc::indoc;
9use tree_sitter::Parser;
10use tree_sitter_graph::ast::File;
11use tree_sitter_graph::functions::Functions;
12use tree_sitter_graph::ExecutionConfig;
13use tree_sitter_graph::ExecutionError;
14use tree_sitter_graph::Identifier;
15use tree_sitter_graph::NoCancellation;
16use tree_sitter_graph::Variables;
17
18fn init_log() {
19 let _ = env_logger::builder()
20 .is_test(true)
21 .format_level(false)
22 .format_target(false)
23 .format_timestamp(None)
24 .try_init(); // try, because earlier test may have already initialized it
25}
26
27fn execute(python_source: &str, dsl_source: &str) -> Result<String, ExecutionError> {
28 init_log();
29 let mut parser = Parser::new();
30 parser
31 .set_language(&tree_sitter_python::LANGUAGE.into())
32 .unwrap();
33 let tree = parser.parse(python_source, None).unwrap();
34 let file =
35 File::from_str(tree_sitter_python::LANGUAGE.into(), dsl_source).expect("Cannot parse file");
36 let functions = Functions::stdlib();
37 let mut globals = Variables::new();
38 globals
39 .add(Identifier::from("filename"), "test.py".into())
40 .map_err(|_| ExecutionError::DuplicateVariable("filename".into()))?;
41 let mut config = ExecutionConfig::new(&functions, &globals);
42 let graph = file.execute(&tree, python_source, &mut config, &NoCancellation)?;
43 let result = graph.pretty_print().to_string();
44 Ok(result)
45}
46
47fn check_execution(python_source: &str, dsl_source: &str, expected_graph: &str) {
48 match execute(python_source, dsl_source) {
49 Ok(actual_graph) => assert_eq!(actual_graph, expected_graph),
50 Err(e) => panic!("Could not execute file: {}", e),
51 }
52}
53
54fn fail_execution(python_source: &str, dsl_source: &str) {
55 if let Ok(_) = execute(python_source, dsl_source) {
56 panic!("Execution succeeded unexpectedly");
57 }
58}
59
60#[test]
61fn can_build_simple_graph() {
62 check_execution(
63 "pass",
64 indoc! {r#"
65 (module) @root
66 {
67 node node0
68 attr (node0) name = "node0", source = @root
69 var node1 = (node)
70 attr (node1) name = "node1"
71 edge node0 -> node1
72 attr (node0 -> node1) precedence = 14
73 node node2
74 attr (node2) name = "node2", parent = node1
75 }
76 "#},
77 indoc! {r#"
78 node 0
79 name: "node0"
80 source: [syntax node module (1, 1)]
81 edge 0 -> 1
82 precedence: 14
83 node 1
84 name: "node1"
85 node 2
86 name: "node2"
87 parent: [graph node 1]
88 "#},
89 );
90}
91
92#[test]
93fn can_scan_strings() {
94 check_execution(
95 "pass",
96 indoc! {r#"
97 (module)
98 {
99 var new_node = #null
100 var current_node = (node)
101
102 scan "alpha/beta/gamma/delta.py" {
103 "([^/]+)/"
104 {
105 set new_node = (node)
106 attr (new_node) name = $1
107 edge current_node -> new_node
108 set current_node = new_node
109 }
110
111 "([^/]+)\\.py$"
112 {
113 set new_node = (node)
114 attr (new_node) name = $1
115 edge current_node -> new_node
116 }
117 }
118 }
119 "#},
120 indoc! {r#"
121 node 0
122 edge 0 -> 1
123 node 1
124 name: "alpha"
125 edge 1 -> 2
126 node 2
127 name: "beta"
128 edge 2 -> 3
129 node 3
130 name: "gamma"
131 edge 3 -> 4
132 node 4
133 name: "delta"
134 "#},
135 );
136}
137
138#[test]
139fn variables_in_scan_arms_are_local() {
140 check_execution(
141 "pass",
142 indoc! {r#"
143 (module)
144 {
145 var current_node = (node)
146
147 scan "alpha/beta/gamma/delta.py" {
148 "([^/]+)/"
149 {
150 let new_node = (node)
151 attr (new_node) name = $1
152 edge current_node -> new_node
153 set current_node = new_node
154 }
155
156 "([^/]+)\\.py$"
157 {
158 let new_node = (node)
159 attr (new_node) name = $1
160 edge current_node -> new_node
161 }
162 }
163 }
164 "#},
165 indoc! {r#"
166 node 0
167 edge 0 -> 1
168 node 1
169 name: "alpha"
170 edge 1 -> 2
171 node 2
172 name: "beta"
173 edge 2 -> 3
174 node 3
175 name: "gamma"
176 edge 3 -> 4
177 node 4
178 name: "delta"
179 "#},
180 );
181}
182
183#[test]
184fn scoped_variables_carry_across_stanzas() {
185 check_execution(
186 indoc! {r#"
187 import a
188 from b import c
189 print(a.d.f)
190 "#},
191 indoc! {r#"
192 (identifier) @id
193 {
194 let @id.node = (node)
195 }
196
197 (identifier) @id
198 {
199 attr (@id.node) name = (source-text @id)
200 }
201 "#},
202 indoc! {r#"
203 node 0
204 name: "a"
205 node 1
206 name: "b"
207 node 2
208 name: "c"
209 node 3
210 name: "print"
211 node 4
212 name: "a"
213 node 5
214 name: "d"
215 node 6
216 name: "f"
217 "#},
218 );
219}
220
221#[test]
222fn can_match_stanza_multiple_times() {
223 check_execution(
224 indoc! {r#"
225 import a
226 from b import c
227 print(a.d.f)
228 "#},
229 indoc! {r#"
230 (identifier) @id
231 {
232 node new_node
233 attr (new_node) name = (source-text @id)
234 }
235 "#},
236 indoc! {r#"
237 node 0
238 name: "a"
239 node 1
240 name: "b"
241 node 2
242 name: "c"
243 node 3
244 name: "print"
245 node 4
246 name: "a"
247 node 5
248 name: "d"
249 node 6
250 name: "f"
251 "#},
252 );
253}
254
255#[test]
256fn can_use_global_variable() {
257 check_execution(
258 "pass",
259 indoc! {r#"
260 global filename
261
262 (module)
263 {
264 node n
265 attr (n) filename = filename
266 }
267 "#},
268 indoc! {r#"
269 node 0
270 filename: "test.py"
271 "#},
272 );
273}
274
275#[test]
276fn can_omit_global_variable_with_default() {
277 check_execution(
278 "pass",
279 indoc! {r#"
280 global pkgname = ""
281
282 (module)
283 {
284 node n
285 attr (n) pkgname = pkgname
286 }
287 "#},
288 indoc! {r#"
289 node 0
290 pkgname: ""
291 "#},
292 );
293}
294
295#[test]
296fn cannot_omit_global_variable() {
297 fail_execution(
298 "pass",
299 indoc! {r#"
300 global root
301
302 (identifier) {
303 node n
304 edge n -> root
305 }
306 "#},
307 );
308}
309
310#[test]
311fn cannot_pass_string_to_global_list_variable() {
312 fail_execution(
313 "pass",
314 indoc! {r#"
315 global filename*
316 "#},
317 );
318}
319
320#[test]
321fn can_use_variable_multiple_times() {
322 check_execution(
323 "pass",
324 indoc! {r#"
325 (module)
326 {
327 let x = (node)
328 let y = x
329 let z = x
330 }
331 "#},
332 indoc! {r#"
333 node 0
334 "#},
335 );
336}
337
338#[test]
339fn can_nest_function_calls() {
340 check_execution(
341 "pass",
342 indoc! {r#"
343 (module)
344 {
345 node node0
346 attr (node0) val = (replace "accacc" (replace "abc" "b" "c") (replace "abc" "a" "b"))
347 }
348 "#},
349 indoc! {r#"
350 node 0
351 val: "bbcbbc"
352 "#},
353 );
354}
355
356#[test]
357fn cannot_use_nullable_regex() {
358 fail_execution(
359 "pass",
360 indoc! {r#"
361 (module)
362 {
363 scan "abc" {
364 "^\\b" {
365 }
366 }
367 node n
368 }
369 "#},
370 );
371}
372
373#[test]
374fn can_create_present_optional_capture() {
375 check_execution(
376 "pass",
377 indoc! {r#"
378 (module (_)? @stmts)
379 {
380 node n
381 attr (n) stmts = @stmts
382 }
383 "#},
384 indoc! {r#"
385 node 0
386 stmts: [syntax node pass_statement (1, 1)]
387 "#},
388 );
389}
390
391#[test]
392fn can_create_missing_optional_capture() {
393 check_execution(
394 indoc! {r#"
395 "#},
396 indoc! {r#"
397 (module (_)? @stmts)
398 {
399 node n
400 attr (n) stmts = @stmts
401 }
402 "#},
403 indoc! {r#"
404 node 0
405 stmts: #null
406 "#},
407 );
408}
409
410#[test]
411fn can_create_empty_list_capture() {
412 check_execution(
413 indoc! {r#"
414 "#},
415 indoc! {r#"
416 (module (_)* @stmts)
417 {
418 node n
419 attr (n) stmts = @stmts
420 }
421 "#},
422 indoc! {r#"
423 node 0
424 stmts: []
425 "#},
426 );
427}
428
429#[test]
430fn can_create_nonempty_list_capture() {
431 check_execution(
432 indoc! {r#"
433 pass
434 pass
435 "#},
436 indoc! {r#"
437 (module (_)+ @stmts)
438 {
439 node n
440 attr (n) stmts = @stmts
441 }
442 "#},
443 indoc! {r#"
444 node 0
445 stmts: [[syntax node pass_statement (1, 1)], [syntax node pass_statement (2, 1)]]
446 "#},
447 );
448}
449
450#[test]
451fn can_execute_if_some() {
452 check_execution(
453 "pass",
454 indoc! {r#"
455 (module (pass_statement)? @x)
456 {
457 node node0
458 if some @x {
459 attr (node0) val = 0
460 } else {
461 attr (node0) val = 1
462 }
463 }
464 "#},
465 indoc! {r#"
466 node 0
467 val: 0
468 "#},
469 );
470}
471
472#[test]
473fn can_execute_if_none() {
474 check_execution(
475 "pass",
476 indoc! {r#"
477 (module (import_statement)? @x)
478 {
479 node node0
480 if none @x {
481 attr (node0) val = 0
482 } else {
483 attr (node0) val = 1
484 }
485 }
486 "#},
487 indoc! {r#"
488 node 0
489 val: 0
490 "#},
491 );
492}
493
494#[test]
495fn can_execute_if_some_and_none() {
496 check_execution(
497 "pass",
498 indoc! {r#"
499 (module (import_statement)? @x (pass_statement)? @y)
500 {
501 node node0
502 if none @x, some @y {
503 attr (node0) val = 1
504 } elif some @y {
505 attr (node0) val = 0
506 }
507 }
508 "#},
509 indoc! {r#"
510 node 0
511 val: 1
512 "#},
513 );
514}
515
516#[test]
517fn can_execute_elif() {
518 check_execution(
519 "pass",
520 indoc! {r#"
521 (module (import_statement)? @x (pass_statement)? @y)
522 {
523 node node0
524 if some @x {
525 attr (node0) val = 0
526 } elif some @y {
527 attr (node0) val = 1
528 }
529 }
530 "#},
531 indoc! {r#"
532 node 0
533 val: 1
534 "#},
535 );
536}
537
538#[test]
539fn can_execute_else() {
540 check_execution(
541 "pass",
542 indoc! {r#"
543 (module (import_statement)? @x)
544 {
545 node node0
546 if some @x {
547 attr (node0) val = 0
548 } else {
549 attr (node0) val = 1
550 }
551 }
552 "#},
553 indoc! {r#"
554 node 0
555 val: 1
556 "#},
557 );
558}
559
560#[test]
561fn can_execute_if_literal() {
562 check_execution(
563 "pass",
564 indoc! {r#"
565 (module (import_statement)?)
566 {
567 node node0
568 if #true {
569 attr (node0) val = 0
570 } else {
571 attr (node0) val = 1
572 }
573 }
574 "#},
575 indoc! {r#"
576 node 0
577 val: 0
578 "#},
579 );
580}
581
582#[test]
583fn skip_if_without_true_conditions() {
584 check_execution(
585 "pass",
586 indoc! {r#"
587 (module (import_statement)? @x (import_statement)? @y)
588 {
589 node node0
590 if some @x {
591 attr (node0) val = 0
592 } elif some @y {
593 attr (node0) val = 1
594 }
595 }
596 "#},
597 indoc! {r#"
598 node 0
599 "#},
600 );
601}
602
603#[test]
604fn variables_are_local_in_if_body() {
605 check_execution(
606 r#"
607 pass
608 "#,
609 indoc! {r#"
610 (module (pass_statement)? @x)
611 {
612 let n = 1
613 if some @x {
614 let n = 2
615 }
616 node node0
617 attr (node0) val = n
618 }
619 "#},
620 indoc! {r#"
621 node 0
622 val: 1
623 "#},
624 );
625}
626
627#[test]
628fn variables_do_not_escape_if_body() {
629 check_execution(
630 r#"
631 pass
632 "#,
633 indoc! {r#"
634 (module (pass_statement)? @x)
635 {
636 var n = 1
637 if some @x {
638 var n = 2
639 }
640 node node0
641 attr (node0) val = n
642 }
643 "#},
644 indoc! {r#"
645 node 0
646 val: 1
647 "#},
648 );
649}
650
651#[test]
652fn variables_are_inherited_in_if_body() {
653 check_execution(
654 r#"
655 pass
656 "#,
657 indoc! {r#"
658 (module (pass_statement)? @x)
659 {
660 var n = 1
661 if some @x {
662 set n = (plus n 1)
663 }
664 node node0
665 attr (node0) val = n
666 }
667 "#},
668 indoc! {r#"
669 node 0
670 val: 2
671 "#},
672 );
673}
674
675#[test]
676fn can_execute_for_in_nonempty_list_capture() {
677 check_execution(
678 r#"
679 pass
680 pass
681 pass
682 "#,
683 indoc! {r#"
684 (module (pass_statement)* @xs)
685 {
686 var n = 0
687 for x in @xs {
688 set n = (plus n 1)
689 }
690 node node0
691 attr (node0) val = n
692 }
693 "#},
694 indoc! {r#"
695 node 0
696 val: 3
697 "#},
698 );
699}
700
701#[test]
702fn can_execute_for_in_empty_list_capture() {
703 check_execution(
704 r#"
705 pass
706 "#,
707 indoc! {r#"
708 (module (import_statement)* @xs)
709 {
710 var n = 0
711 for x in @xs {
712 set n = (plus n 1)
713 }
714 node node0
715 attr (node0) val = n
716 }
717 "#},
718 indoc! {r#"
719 node 0
720 val: 0
721 "#},
722 );
723}
724
725#[test]
726fn can_execute_for_in_list_literal() {
727 check_execution(
728 r#"
729 pass
730 "#,
731 indoc! {r#"
732 (module)
733 {
734 var n = 0
735 for x in [#null, #null, #null] {
736 set n = (plus n 1)
737 }
738 node node0
739 attr (node0) val = n
740 }
741 "#},
742 indoc! {r#"
743 node 0
744 val: 3
745 "#},
746 );
747}
748
749#[test]
750fn variables_are_local_in_for_in_body() {
751 check_execution(
752 r#"
753 pass
754 "#,
755 indoc! {r#"
756 (module (pass_statement)* @xs)
757 {
758 let n = 1
759 for x in @xs {
760 let n = 2
761 }
762 node node0
763 attr (node0) val = n
764 }
765 "#},
766 indoc! {r#"
767 node 0
768 val: 1
769 "#},
770 );
771}
772
773#[test]
774fn variables_do_not_escape_for_in_body() {
775 check_execution(
776 r#"
777 pass
778 "#,
779 indoc! {r#"
780 (module (pass_statement)* @xs)
781 {
782 var n = 1
783 for x in @xs {
784 var n = 2
785 }
786 node node0
787 attr (node0) val = n
788 }
789 "#},
790 indoc! {r#"
791 node 0
792 val: 1
793 "#},
794 );
795}
796
797#[test]
798fn variables_are_inherited_in_for_in_body() {
799 check_execution(
800 r#"
801 pass
802 pass
803 pass
804 "#,
805 indoc! {r#"
806 (module (pass_statement)+ @xs)
807 {
808 var n = 0
809 for x in @xs {
810 set n = (plus n 1)
811 }
812 node node0
813 attr (node0) val = n
814 }
815 "#},
816 indoc! {r#"
817 node 0
818 val: 3
819 "#},
820 );
821}
822
823#[test]
824fn can_execute_list_comprehension() {
825 check_execution(
826 r#"
827 pass
828 pass
829 pass
830 "#,
831 indoc! {r#"
832 (module (pass_statement)* @xs)
833 {
834 node node0
835 attr (node0) val = [ (named-child-index x) for x in @xs ]
836 }
837 "#},
838 indoc! {r#"
839 node 0
840 val: [0, 1, 2]
841 "#},
842 );
843}
844
845#[test]
846fn can_execute_set_comprehension() {
847 check_execution(
848 r#"
849 pass
850 pass
851 pass
852 "#,
853 indoc! {r#"
854 (module (pass_statement)* @xs)
855 {
856 node node0
857 attr (node0) val = { (source-text x) for x in @xs }
858 }
859 "#},
860 indoc! {r#"
861 node 0
862 val: {"pass"}
863 "#},
864 );
865}
866
867#[test]
868fn can_execute_scan_of_local_call_expression() {
869 check_execution(
870 r#"
871 def get_f():
872 pass
873 "#,
874 indoc! {r#"
875 (function_definition
876 name: (identifier) @name)
877 {
878 node n
879 scan (source-text @name) {
880 "get_.*" {
881 attr (n) is_getter = #true
882 }
883 }
884 }
885 "#},
886 indoc! {r#"
887 node 0
888 is_getter: #true
889 "#},
890 );
891}
892
893#[test]
894fn can_execute_scan_of_local_variable() {
895 check_execution(
896 r#"
897 def get_f():
898 pass
899 "#,
900 indoc! {r#"
901 (function_definition
902 name: (identifier) @name)
903 {
904 node n
905 let val = (source-text @name)
906 scan val {
907 "get_.*" {
908 attr (n) is_getter = #true
909 }
910 }
911 }
912 "#},
913 indoc! {r#"
914 node 0
915 is_getter: #true
916 "#},
917 );
918}
919
920#[test]
921fn can_execute_shorthand() {
922 check_execution(
923 indoc! { r#"
924 def get_f():
925 pass
926 "#},
927 indoc! {r#"
928 attribute def = x => source_node = x, symbol = (source-text x)
929 (function_definition name: (identifier) @name) {
930 node n
931 attr (n) def = @name
932 }
933 "#},
934 indoc! {r#"
935 node 0
936 source_node: [syntax node identifier (1, 5)]
937 symbol: "get_f"
938 "#},
939 );
940}
941
942#[test]
943fn can_access_inherited_attribute() {
944 check_execution(
945 indoc! { r#"
946 def get_f():
947 pass
948 "#},
949 indoc! {r#"
950 inherit .test
951 (function_definition)@def {
952 node @def.test
953 attr (@def.test) in_def
954 }
955 (pass_statement)@pass {
956 attr (@pass.test) in_pass
957 }
958 "#},
959 indoc! {r#"
960 node 0
961 in_def: #true
962 in_pass: #true
963 "#},
964 );
965}
966
967#[test]
968fn can_overwrite_inherited_attribute() {
969 check_execution(
970 indoc! { r#"
971 def get_f():
972 pass
973 "#},
974 indoc! {r#"
975 inherit .test
976 (function_definition)@def {
977 node @def.test
978 attr (@def.test) in_def
979 }
980 (pass_statement)@pass {
981 node @pass.test
982 }
983 (pass_statement)@pass {
984 attr (@pass.test) in_pass
985 }
986 "#},
987 indoc! {r#"
988 node 0
989 in_def: #true
990 node 1
991 in_pass: #true
992 "#},
993 );
994}
995
996#[test]
997fn cannot_access_non_inherited_variable() {
998 fail_execution(
999 indoc! { r#"
1000 def get_f():
1001 pass
1002 "#},
1003 indoc! {r#"
1004 (function_definition)@def {
1005 node @def.test
1006 }
1007 (pass_statement)@pass {
1008 attr (@pass.test) in_pass
1009 }
1010 "#},
1011 );
1012}
1013
1014#[test]
1015fn can_add_edge_twice() {
1016 check_execution(
1017 indoc! { r#"
1018 pass
1019 "#},
1020 indoc! {r#"
1021 (module) {
1022 node n1;
1023 node n2;
1024 edge n1 -> n2;
1025 edge n1 -> n2;
1026 }
1027 "#},
1028 indoc! {r#"
1029 node 0
1030 edge 0 -> 1
1031 node 1
1032 "#},
1033 );
1034}
1035
1036#[test]
1037fn can_set_node_attribute_value_twice() {
1038 check_execution(
1039 indoc! { r#"
1040 pass
1041 "#},
1042 indoc! {r#"
1043 (module) {
1044 node n;
1045 attr (n) foo = #true;
1046 }
1047 "#},
1048 indoc! {r#"
1049 node 0
1050 foo: #true
1051 "#},
1052 );
1053}
1054
1055#[test]
1056fn cannot_change_attribute_value() {
1057 check_execution(
1058 indoc! { r#"
1059 pass
1060 "#},
1061 indoc! {r#"
1062 (module) {
1063 node n1;
1064 node n2;
1065 edge n1 -> n2;
1066 attr (n1 -> n2) foo = #true;
1067 }
1068 "#},
1069 indoc! {r#"
1070 node 0
1071 edge 0 -> 1
1072 foo: #true
1073 node 1
1074 "#},
1075 );
1076}