1#![warn(clippy::all, clippy::nursery, clippy::pedantic, future_incompatible, missing_docs, nonstandard_style, rust_2018_idioms, trivial_casts, trivial_numeric_casts, unreachable_pub, unused_qualifications)]
6
7#[allow(clippy::too_many_lines, clippy::use_self)]
10mod bindings {
11 use std::ptr::NonNull;
12
13 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
16}
17
18mod node;
19mod node_ext;
20mod parse_result;
21mod string_query;
22
23use std::ffi::CString;
24use std::ptr::NonNull;
25
26pub use self::bindings::*;
27pub use self::node::{ConstantId, ConstantList, ConstantListIter, Integer, NodeList, NodeListIter};
28pub use self::node_ext::{ConstantPathError, FullName};
29pub use self::parse_result::{Comment, CommentType, Comments, Diagnostic, Diagnostics, Location, MagicComment, MagicComments, ParseResult};
30pub use self::string_query::{InvalidEncodingError, StringQuery};
31
32use ruby_prism_sys::{
33 pm_arena_new, pm_options_command_line_set, pm_options_encoding_locked_set, pm_options_encoding_set, pm_options_filepath_set, pm_options_free, pm_options_frozen_string_literal_set, pm_options_line_set, pm_options_main_script_set, pm_options_new, pm_options_partial_script_set,
34 pm_options_scope_forwarding_set, pm_options_scope_init, pm_options_scope_local_mut, pm_options_scope_mut, pm_options_scopes_init, pm_options_t, pm_options_version_set, pm_parse, pm_parser_new, pm_string_init,
35};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Version {
40 Latest,
42 CRuby3_3,
44 CRuby3_4,
46 #[deprecated(note = "Use Version::CRuby4_0 instead")]
48 CRuby3_5,
49 CRuby4_0,
51 CRuby4_1,
53}
54
55impl Version {
56 unsafe fn set_on(self, opts: *mut pm_options_t) {
59 match self {
60 Self::Latest => {
61 pm_options_version_set(opts, std::ptr::null(), 0);
62 },
63 Self::CRuby3_3 => {
64 pm_options_version_set(opts, c"3.3".as_ptr(), 3);
65 },
66 Self::CRuby3_4 => {
67 pm_options_version_set(opts, c"3.4".as_ptr(), 3);
68 },
69 #[allow(deprecated)]
70 Self::CRuby3_5 | Self::CRuby4_0 => {
71 pm_options_version_set(opts, c"4.0".as_ptr(), 3);
72 },
73 Self::CRuby4_1 => {
74 pm_options_version_set(opts, c"4.1".as_ptr(), 3);
75 },
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum CommandLineFlag {
83 A,
85 E,
87 L,
89 N,
91 P,
93 X,
95}
96
97impl From<CommandLineFlag> for u8 {
98 fn from(flag: CommandLineFlag) -> Self {
99 match flag {
100 CommandLineFlag::A => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_A,
101 CommandLineFlag::E => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_E,
102 CommandLineFlag::L => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_L,
103 CommandLineFlag::N => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_N,
104 CommandLineFlag::P => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_P,
105 CommandLineFlag::X => ruby_prism_sys::PM_OPTIONS_COMMAND_LINE_X,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub enum ScopeForwardingFlag {
113 Positionals,
115 Keywords,
117 Block,
119 All,
121}
122
123impl From<ScopeForwardingFlag> for u8 {
124 fn from(flag: ScopeForwardingFlag) -> Self {
125 match flag {
126 ScopeForwardingFlag::Positionals => ruby_prism_sys::PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS,
127 ScopeForwardingFlag::Keywords => ruby_prism_sys::PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS,
128 ScopeForwardingFlag::Block => ruby_prism_sys::PM_OPTIONS_SCOPE_FORWARDING_BLOCK,
129 ScopeForwardingFlag::All => ruby_prism_sys::PM_OPTIONS_SCOPE_FORWARDING_ALL,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Default)]
136pub struct Scope {
137 locals: Vec<Vec<u8>>,
138 forwarding: Vec<ScopeForwardingFlag>,
139}
140
141impl Scope {
142 #[must_use]
144 pub fn locals(mut self, locals: Vec<Vec<u8>>) -> Self {
145 self.locals = locals;
146 self
147 }
148
149 #[must_use]
151 pub fn forwarding(mut self, forwarding: Vec<ScopeForwardingFlag>) -> Self {
152 self.forwarding = forwarding;
153 self
154 }
155}
156
157#[derive(Debug, Clone, Default)]
159pub struct Options {
160 filepath: Option<String>,
161 line: Option<i32>,
162 encoding: Option<String>,
163 encoding_locked: bool,
164 frozen_string_literal: Option<bool>,
165 command_line: Vec<CommandLineFlag>,
166 version: Option<Version>,
167 main_script: bool,
168 partial_script: bool,
169 scopes: Vec<Scope>,
170}
171
172impl Options {
173 #[must_use]
175 pub fn filepath(mut self, filepath: &str) -> Self {
176 self.filepath = Some(filepath.to_string());
177 self
178 }
179
180 #[must_use]
182 pub const fn line(mut self, line: i32) -> Self {
183 self.line = Some(line);
184 self
185 }
186
187 #[must_use]
189 pub fn encoding(mut self, encoding: &str) -> Self {
190 self.encoding = Some(encoding.to_string());
191 self
192 }
193
194 #[must_use]
196 pub const fn encoding_locked(mut self, locked: bool) -> Self {
197 self.encoding_locked = locked;
198 self
199 }
200
201 #[must_use]
205 pub const fn frozen_string_literal(mut self, frozen: Option<bool>) -> Self {
206 self.frozen_string_literal = frozen;
207 self
208 }
209
210 #[must_use]
212 pub fn command_line(mut self, command_line: Vec<CommandLineFlag>) -> Self {
213 self.command_line = command_line;
214 self
215 }
216
217 #[must_use]
219 pub const fn version(mut self, version: Version) -> Self {
220 self.version = Some(version);
221 self
222 }
223
224 #[must_use]
226 pub const fn main_script(mut self, main_script: bool) -> Self {
227 self.main_script = main_script;
228 self
229 }
230
231 #[must_use]
233 pub const fn partial_script(mut self, partial_script: bool) -> Self {
234 self.partial_script = partial_script;
235 self
236 }
237
238 #[must_use]
240 pub fn scope(mut self, scope: Scope) -> Self {
241 self.scopes.push(scope);
242 self
243 }
244
245 #[must_use]
247 pub fn scopes(mut self, scopes: Vec<Scope>) -> Self {
248 self.scopes = scopes;
249 self
250 }
251
252 #[must_use]
259 pub fn build(self) -> ParseOptions {
260 let opts = unsafe { pm_options_new() };
261
262 let c_filepath = self.filepath.map(|filepath| {
263 let cstring = CString::new(filepath).unwrap();
264 unsafe { pm_options_filepath_set(opts, cstring.as_ptr()) };
265 cstring
266 });
267
268 if let Some(line) = self.line {
269 unsafe { pm_options_line_set(opts, line) };
270 }
271
272 let c_encoding = self.encoding.map(|encoding| {
273 let cstring = CString::new(encoding).unwrap();
274 unsafe { pm_options_encoding_set(opts, cstring.as_ptr()) };
275 cstring
276 });
277
278 if self.encoding_locked {
279 unsafe { pm_options_encoding_locked_set(opts, true) };
280 }
281
282 if let Some(frozen) = self.frozen_string_literal {
283 unsafe { pm_options_frozen_string_literal_set(opts, frozen) };
284 }
285
286 let command_line = self.command_line.iter().fold(0u8, |acc, &flag| acc | u8::from(flag));
287 if command_line != 0 {
288 unsafe { pm_options_command_line_set(opts, command_line) };
289 }
290
291 if let Some(version) = self.version {
292 unsafe { version.set_on(opts) };
293 }
294
295 if self.main_script {
296 unsafe { pm_options_main_script_set(opts, true) };
297 }
298
299 if self.partial_script {
300 unsafe { pm_options_partial_script_set(opts, true) };
301 }
302
303 if !self.scopes.is_empty() {
304 unsafe { pm_options_scopes_init(opts, self.scopes.len()) };
305
306 for (scope_index, scope) in self.scopes.iter().enumerate() {
307 let pm_scope = unsafe { pm_options_scope_mut(opts, scope_index) };
308 unsafe { pm_options_scope_init(pm_scope, scope.locals.len()) };
309
310 for (local_index, local) in scope.locals.iter().enumerate() {
311 let pm_local = unsafe { pm_options_scope_local_mut(pm_scope, local_index) };
312 unsafe { pm_string_init(pm_local, local.as_ptr(), local.len()) };
313 }
314
315 let forwarding = scope.forwarding.iter().fold(0u8, |acc, &flag| acc | u8::from(flag));
316 if forwarding != 0 {
317 unsafe { pm_options_scope_forwarding_set(pm_scope, forwarding) };
318 }
319 }
320 }
321
322 ParseOptions {
323 options: opts,
324 _filepath: c_filepath,
325 _encoding: c_encoding,
326 _scopes: self.scopes,
327 }
328 }
329}
330
331pub struct ParseOptions {
334 options: *mut pm_options_t,
335 _filepath: Option<CString>,
339 _encoding: Option<CString>,
340 _scopes: Vec<Scope>,
342}
343
344impl Drop for ParseOptions {
345 fn drop(&mut self) {
346 unsafe { pm_options_free(self.options) };
347 }
348}
349
350unsafe fn parse_impl(source: &[u8], options: *const pm_options_t) -> ParseResult<'_> {
356 let arena = pm_arena_new();
357 let parser = pm_parser_new(arena, source.as_ptr(), source.len(), options);
358 let node = NonNull::new_unchecked(pm_parse(parser));
359 ParseResult::new(source, arena, parser, node)
360}
361
362#[must_use]
369pub fn parse(source: &[u8]) -> ParseResult<'_> {
370 unsafe { parse_impl(source, std::ptr::null()) }
371}
372
373#[must_use]
381pub fn parse_with_options<'a>(source: &'a [u8], options: &'a ParseOptions) -> ParseResult<'a> {
382 unsafe { parse_impl(source, options.options) }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::parse;
388
389 #[test]
390 fn comments_test() {
391 let source = "# comment 1\n# comment 2\n# comment 3\n";
392 let result = parse(source.as_ref());
393
394 for comment in result.comments() {
395 assert_eq!(super::CommentType::InlineComment, comment.type_());
396 let text = std::str::from_utf8(comment.text()).unwrap();
397 assert!(text.starts_with("# comment"));
398 }
399 }
400
401 #[test]
402 fn line_offsets_test() {
403 let source = "";
404 let result = parse(source.as_ref());
405
406 let expected: [u32; 1] = [0];
407 assert_eq!(expected, result.line_offsets());
408
409 let source = "1 + 1";
410 let result = parse(source.as_ref());
411
412 let expected: [u32; 1] = [0];
413 assert_eq!(expected, result.line_offsets());
414
415 let source = "begin\n1 + 1\n2 + 2\nend";
416 let result = parse(source.as_ref());
417
418 let expected: [u32; 4] = [0, 6, 12, 18];
419 assert_eq!(expected, result.line_offsets());
420 }
421
422 #[test]
423 fn magic_comments_test() {
424 use crate::MagicComment;
425
426 let source = "# typed: ignore\n# typed:true\n#typed: strict\n";
427 let result = parse(source.as_ref());
428
429 let comments: Vec<MagicComment<'_>> = result.magic_comments().collect();
430 assert_eq!(3, comments.len());
431
432 assert_eq!(b"typed", comments[0].key());
433 assert_eq!(b"ignore", comments[0].value());
434
435 assert_eq!(b"typed", comments[1].key());
436 assert_eq!(b"true", comments[1].value());
437
438 assert_eq!(b"typed", comments[2].key());
439 assert_eq!(b"strict", comments[2].value());
440 }
441
442 #[test]
443 fn data_loc_test() {
444 let source = "1";
445 let result = parse(source.as_ref());
446 let data_loc = result.data_loc();
447 assert!(data_loc.is_none());
448
449 let source = "__END__\nabc\n";
450 let result = parse(source.as_ref());
451 let data_loc = result.data_loc().unwrap();
452 let slice = std::str::from_utf8(result.as_slice(&data_loc)).unwrap();
453 assert_eq!(slice, "__END__\nabc\n");
454
455 let source = "1\n2\n3\n__END__\nabc\ndef\n";
456 let result = parse(source.as_ref());
457 let data_loc = result.data_loc().unwrap();
458 let slice = std::str::from_utf8(result.as_slice(&data_loc)).unwrap();
459 assert_eq!(slice, "__END__\nabc\ndef\n");
460 }
461
462 #[test]
463 fn location_slice_test() {
464 let source = "111 + 222 + 333";
465 let result = parse(source.as_ref());
466
467 let node = result.node();
468 let node = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
469 let node = node.as_call_node().unwrap().receiver().unwrap();
470 let plus = node.as_call_node().unwrap();
471 let node = plus.arguments().unwrap().arguments().iter().next().unwrap();
472
473 let location = node.as_integer_node().unwrap().location();
474 let slice = std::str::from_utf8(result.as_slice(&location)).unwrap();
475
476 assert_eq!(slice, "222");
477 assert_eq!(6, location.start);
478 assert_eq!(location.start, location.start());
479 assert_eq!(9, location.end());
480
481 let recv_loc = plus.receiver().unwrap().location();
482 assert_eq!(recv_loc.as_slice(), b"111");
483 assert_eq!(0, recv_loc.start);
484 assert_eq!(3, recv_loc.end());
485
486 let joined = recv_loc.join(&location).unwrap();
487 assert_eq!(joined.as_slice(), b"111 + 222");
488
489 let not_joined = location.join(&recv_loc);
490 assert!(not_joined.is_none());
491
492 {
493 let result = parse(source.as_ref());
494 let node = result.node();
495 let node = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
496 let node = node.as_call_node().unwrap().receiver().unwrap();
497 let plus = node.as_call_node().unwrap();
498 let node = plus.arguments().unwrap().arguments().iter().next().unwrap();
499
500 let location = node.as_integer_node().unwrap().location();
501 let not_joined = recv_loc.join(&location);
502 assert!(not_joined.is_none());
503
504 let not_joined = location.join(&recv_loc);
505 assert!(not_joined.is_none());
506 }
507
508 let location = node.location();
509 let slice = std::str::from_utf8(result.as_slice(&location)).unwrap();
510
511 assert_eq!(slice, "222");
512
513 let slice = std::str::from_utf8(location.as_slice()).unwrap();
514
515 assert_eq!(slice, "222");
516 }
517
518 #[test]
519 fn location_line_column_test() {
520 let source = "first\nsecond\nthird";
521 let result = parse(source.as_ref());
522
523 let node = result.node();
524 let program = node.as_program_node().unwrap();
525 let statements = program.statements().body();
526 let mut iter = statements.iter();
527
528 let _first = iter.next().unwrap();
529 let second = iter.next().unwrap();
530 let third = iter.next().unwrap();
531
532 let second_loc = second.location();
533 assert_eq!(second_loc.start_line(), 2);
534 assert_eq!(second_loc.end_line(), 2);
535 assert_eq!(second_loc.start_column(), 0);
536 assert_eq!(second_loc.end_column(), 6);
537
538 let third_loc = third.location();
539 assert_eq!(third_loc.start_line(), 3);
540 assert_eq!(third_loc.end_line(), 3);
541 assert_eq!(third_loc.start_column(), 0);
542 assert_eq!(third_loc.end_column(), 5);
543 }
544
545 #[test]
546 fn location_chop_test() {
547 let result = parse(b"foo");
548 let mut location = result.node().as_program_node().unwrap().location();
549
550 assert_eq!(location.chop().as_slice(), b"fo");
551 assert_eq!(location.chop().chop().chop().as_slice(), b"");
552
553 for _ in 0..10 {
555 location = location.chop();
556 }
557 assert_eq!(location.as_slice(), b"");
558 }
559
560 #[test]
561 fn visitor_test() {
562 use super::{visit_interpolated_regular_expression_node, visit_regular_expression_node, InterpolatedRegularExpressionNode, RegularExpressionNode, Visit};
563
564 struct RegularExpressionVisitor {
565 count: usize,
566 }
567
568 impl Visit<'_> for RegularExpressionVisitor {
569 fn visit_interpolated_regular_expression_node(&mut self, node: &InterpolatedRegularExpressionNode<'_>) {
570 self.count += 1;
571 visit_interpolated_regular_expression_node(self, node);
572 }
573
574 fn visit_regular_expression_node(&mut self, node: &RegularExpressionNode<'_>) {
575 self.count += 1;
576 visit_regular_expression_node(self, node);
577 }
578 }
579
580 let source = "# comment 1\n# comment 2\nmodule Foo; class Bar; /abc #{/def/}/; end; end";
581 let result = parse(source.as_ref());
582
583 let mut visitor = RegularExpressionVisitor { count: 0 };
584 visitor.visit(&result.node());
585
586 assert_eq!(visitor.count, 2);
587 }
588
589 #[test]
590 fn node_upcast_test() {
591 use super::Node;
592
593 let source = "module Foo; end";
594 let result = parse(source.as_ref());
595
596 let node = result.node();
597 let upcast_node = node.as_program_node().unwrap().as_node();
598 assert!(matches!(upcast_node, Node::ProgramNode { .. }));
599
600 let node = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
601 let upcast_node = node.as_module_node().unwrap().as_node();
602 assert!(matches!(upcast_node, Node::ModuleNode { .. }));
603 }
604
605 #[test]
606 fn constant_id_test() {
607 let source = "module Foo; x = 1; y = 2; end";
608 let result = parse(source.as_ref());
609
610 let node = result.node();
611 assert_eq!(node.as_program_node().unwrap().statements().body().len(), 1);
612 assert!(!node.as_program_node().unwrap().statements().body().is_empty());
613 let module = node.as_program_node().and_then(|pn| pn.statements().body().first()).unwrap();
614 let module = module.as_module_node().unwrap();
615
616 assert_eq!(module.locals().len(), 2);
617 assert!(!module.locals().is_empty());
618
619 assert_eq!(module.locals().first().unwrap().as_slice(), b"x");
620 assert_eq!(module.locals().last().unwrap().as_slice(), b"y");
621
622 let source = "module Foo; end";
623 let result = parse(source.as_ref());
624
625 let node = result.node();
626 assert_eq!(node.as_program_node().unwrap().statements().body().len(), 1);
627 assert!(!node.as_program_node().unwrap().statements().body().is_empty());
628 let module = node.as_program_node().and_then(|pn| pn.statements().body().first()).unwrap();
629 let module = module.as_module_node().unwrap();
630
631 assert_eq!(module.locals().len(), 0);
632 assert!(module.locals().is_empty());
633 }
634
635 #[test]
636 fn optional_loc_test() {
637 let source = r"
638module Example
639 x = call_func(3, 4)
640 y = x.call_func 5, 6
641end
642";
643 let result = parse(source.as_ref());
644
645 let node = result.node();
646 let module = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
647 let module = module.as_module_node().unwrap();
648 let body = module.body();
649 let writes = body.iter().next().unwrap().as_statements_node().unwrap().body().iter().collect::<Vec<_>>();
650 assert_eq!(writes.len(), 2);
651
652 let asgn = &writes[0];
653 let call = asgn.as_local_variable_write_node().unwrap().value();
654 let call = call.as_call_node().unwrap();
655
656 let call_operator_loc = call.call_operator_loc();
657 assert!(call_operator_loc.is_none());
658 let closing_loc = call.closing_loc();
659 assert!(closing_loc.is_some());
660
661 let asgn = &writes[1];
662 let call = asgn.as_local_variable_write_node().unwrap().value();
663 let call = call.as_call_node().unwrap();
664
665 let call_operator_loc = call.call_operator_loc();
666 assert!(call_operator_loc.is_some());
667 let closing_loc = call.closing_loc();
668 assert!(closing_loc.is_none());
669 }
670
671 #[test]
672 fn frozen_strings_test() {
673 let source = r#"
674# frozen_string_literal: true
675"foo"
676"#;
677 let result = parse(source.as_ref());
678 assert!(result.frozen_string_literals());
679
680 let source = "3";
681 let result = parse(source.as_ref());
682 assert!(!result.frozen_string_literals());
683 }
684
685 #[test]
686 fn string_flags_test() {
687 let source = r#"
688# frozen_string_literal: true
689"foo"
690"#;
691 let result = parse(source.as_ref());
692
693 let node = result.node();
694 let string = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
695 let string = string.as_string_node().unwrap();
696 assert!(string.is_frozen());
697
698 let source = r#"
699"foo"
700"#;
701 let result = parse(source.as_ref());
702
703 let node = result.node();
704 let string = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
705 let string = string.as_string_node().unwrap();
706 assert!(!string.is_frozen());
707 }
708
709 #[test]
710 fn call_flags_test() {
711 let source = r"
712x
713";
714 let result = parse(source.as_ref());
715
716 let node = result.node();
717 let call = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
718 let call = call.as_call_node().unwrap();
719 assert!(call.is_variable_call());
720
721 let source = r"
722x&.foo
723";
724 let result = parse(source.as_ref());
725
726 let node = result.node();
727 let call = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
728 let call = call.as_call_node().unwrap();
729 assert!(call.is_safe_navigation());
730 }
731
732 #[test]
733 fn integer_flags_test() {
734 let source = r"
7350b1
736";
737 let result = parse(source.as_ref());
738
739 let node = result.node();
740 let i = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
741 let i = i.as_integer_node().unwrap();
742 assert!(i.is_binary());
743 assert!(!i.is_decimal());
744 assert!(!i.is_octal());
745 assert!(!i.is_hexadecimal());
746
747 let source = r"
7481
749";
750 let result = parse(source.as_ref());
751
752 let node = result.node();
753 let i = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
754 let i = i.as_integer_node().unwrap();
755 assert!(!i.is_binary());
756 assert!(i.is_decimal());
757 assert!(!i.is_octal());
758 assert!(!i.is_hexadecimal());
759
760 let source = r"
7610o1
762";
763 let result = parse(source.as_ref());
764
765 let node = result.node();
766 let i = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
767 let i = i.as_integer_node().unwrap();
768 assert!(!i.is_binary());
769 assert!(!i.is_decimal());
770 assert!(i.is_octal());
771 assert!(!i.is_hexadecimal());
772
773 let source = r"
7740x1
775";
776 let result = parse(source.as_ref());
777
778 let node = result.node();
779 let i = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
780 let i = i.as_integer_node().unwrap();
781 assert!(!i.is_binary());
782 assert!(!i.is_decimal());
783 assert!(!i.is_octal());
784 assert!(i.is_hexadecimal());
785 }
786
787 #[test]
788 fn range_flags_test() {
789 let source = r"
7900..1
791";
792 let result = parse(source.as_ref());
793
794 let node = result.node();
795 let range = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
796 let range = range.as_range_node().unwrap();
797 assert!(!range.is_exclude_end());
798
799 let source = r"
8000...1
801";
802 let result = parse(source.as_ref());
803
804 let node = result.node();
805 let range = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
806 let range = range.as_range_node().unwrap();
807 assert!(range.is_exclude_end());
808 }
809
810 #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
811 #[test]
812 fn regex_flags_test() {
813 let source = r"
814/a/i
815";
816 let result = parse(source.as_ref());
817
818 let node = result.node();
819 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
820 let regex = regex.as_regular_expression_node().unwrap();
821 assert!(regex.is_ignore_case());
822 assert!(!regex.is_extended());
823 assert!(!regex.is_multi_line());
824 assert!(!regex.is_euc_jp());
825 assert!(!regex.is_ascii_8bit());
826 assert!(!regex.is_windows_31j());
827 assert!(!regex.is_utf_8());
828 assert!(!regex.is_once());
829
830 let source = r"
831/a/x
832";
833 let result = parse(source.as_ref());
834
835 let node = result.node();
836 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
837 let regex = regex.as_regular_expression_node().unwrap();
838 assert!(!regex.is_ignore_case());
839 assert!(regex.is_extended());
840 assert!(!regex.is_multi_line());
841 assert!(!regex.is_euc_jp());
842 assert!(!regex.is_ascii_8bit());
843 assert!(!regex.is_windows_31j());
844 assert!(!regex.is_utf_8());
845 assert!(!regex.is_once());
846
847 let source = r"
848/a/m
849";
850 let result = parse(source.as_ref());
851
852 let node = result.node();
853 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
854 let regex = regex.as_regular_expression_node().unwrap();
855 assert!(!regex.is_ignore_case());
856 assert!(!regex.is_extended());
857 assert!(regex.is_multi_line());
858 assert!(!regex.is_euc_jp());
859 assert!(!regex.is_ascii_8bit());
860 assert!(!regex.is_windows_31j());
861 assert!(!regex.is_utf_8());
862 assert!(!regex.is_once());
863
864 let source = r"
865/a/e
866";
867 let result = parse(source.as_ref());
868
869 let node = result.node();
870 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
871 let regex = regex.as_regular_expression_node().unwrap();
872 assert!(!regex.is_ignore_case());
873 assert!(!regex.is_extended());
874 assert!(!regex.is_multi_line());
875 assert!(regex.is_euc_jp());
876 assert!(!regex.is_ascii_8bit());
877 assert!(!regex.is_windows_31j());
878 assert!(!regex.is_utf_8());
879 assert!(!regex.is_once());
880
881 let source = r"
882/a/n
883";
884 let result = parse(source.as_ref());
885
886 let node = result.node();
887 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
888 let regex = regex.as_regular_expression_node().unwrap();
889 assert!(!regex.is_ignore_case());
890 assert!(!regex.is_extended());
891 assert!(!regex.is_multi_line());
892 assert!(!regex.is_euc_jp());
893 assert!(regex.is_ascii_8bit());
894 assert!(!regex.is_windows_31j());
895 assert!(!regex.is_utf_8());
896 assert!(!regex.is_once());
897
898 let source = r"
899/a/s
900";
901 let result = parse(source.as_ref());
902
903 let node = result.node();
904 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
905 let regex = regex.as_regular_expression_node().unwrap();
906 assert!(!regex.is_ignore_case());
907 assert!(!regex.is_extended());
908 assert!(!regex.is_multi_line());
909 assert!(!regex.is_euc_jp());
910 assert!(!regex.is_ascii_8bit());
911 assert!(regex.is_windows_31j());
912 assert!(!regex.is_utf_8());
913 assert!(!regex.is_once());
914
915 let source = r"
916/a/u
917";
918 let result = parse(source.as_ref());
919
920 let node = result.node();
921 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
922 let regex = regex.as_regular_expression_node().unwrap();
923 assert!(!regex.is_ignore_case());
924 assert!(!regex.is_extended());
925 assert!(!regex.is_multi_line());
926 assert!(!regex.is_euc_jp());
927 assert!(!regex.is_ascii_8bit());
928 assert!(!regex.is_windows_31j());
929 assert!(regex.is_utf_8());
930 assert!(!regex.is_once());
931
932 let source = r"
933/a/o
934";
935 let result = parse(source.as_ref());
936
937 let node = result.node();
938 let regex = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
939 let regex = regex.as_regular_expression_node().unwrap();
940 assert!(!regex.is_ignore_case());
941 assert!(!regex.is_extended());
942 assert!(!regex.is_multi_line());
943 assert!(!regex.is_euc_jp());
944 assert!(!regex.is_ascii_8bit());
945 assert!(!regex.is_windows_31j());
946 assert!(!regex.is_utf_8());
947 assert!(regex.is_once());
948 }
949
950 #[test]
951 fn visitor_traversal_test() {
952 use crate::{Node, Visit};
953
954 #[derive(Default)]
955 struct NodeCounts {
956 pre_parent: usize,
957 post_parent: usize,
958 pre_leaf: usize,
959 post_leaf: usize,
960 }
961
962 #[derive(Default)]
963 struct CountingVisitor {
964 counts: NodeCounts,
965 }
966
967 impl Visit<'_> for CountingVisitor {
968 fn visit_branch_node_enter(&mut self, _node: Node<'_>) {
969 self.counts.pre_parent += 1;
970 }
971
972 fn visit_branch_node_leave(&mut self) {
973 self.counts.post_parent += 1;
974 }
975
976 fn visit_leaf_node_enter(&mut self, _node: Node<'_>) {
977 self.counts.pre_leaf += 1;
978 }
979
980 fn visit_leaf_node_leave(&mut self) {
981 self.counts.post_leaf += 1;
982 }
983 }
984
985 let source = r"
986module Example
987 x = call_func(3, 4)
988 y = x.call_func 5, 6
989end
990";
991 let result = parse(source.as_ref());
992 let node = result.node();
993 let mut visitor = CountingVisitor::default();
994 visitor.visit(&node);
995
996 assert_eq!(7, visitor.counts.pre_parent);
997 assert_eq!(7, visitor.counts.post_parent);
998 assert_eq!(6, visitor.counts.pre_leaf);
999 assert_eq!(6, visitor.counts.post_leaf);
1000 }
1001
1002 #[test]
1003 fn visitor_lifetime_test() {
1004 use crate::{Node, Visit};
1005
1006 #[derive(Default)]
1007 struct StackingNodeVisitor<'a> {
1008 stack: Vec<Node<'a>>,
1009 max_depth: usize,
1010 }
1011
1012 impl<'pr> Visit<'pr> for StackingNodeVisitor<'pr> {
1013 fn visit_branch_node_enter(&mut self, node: Node<'pr>) {
1014 self.stack.push(node);
1015 }
1016
1017 fn visit_branch_node_leave(&mut self) {
1018 self.stack.pop();
1019 }
1020
1021 fn visit_leaf_node_leave(&mut self) {
1022 self.max_depth = self.max_depth.max(self.stack.len());
1023 }
1024 }
1025
1026 let source = r"
1027module Example
1028 x = call_func(3, 4)
1029 y = x.call_func 5, 6
1030end
1031";
1032 let result = parse(source.as_ref());
1033 let node = result.node();
1034 let mut visitor = StackingNodeVisitor::default();
1035 visitor.visit(&node);
1036
1037 assert_eq!(0, visitor.stack.len());
1038 assert_eq!(5, visitor.max_depth);
1039 }
1040
1041 #[test]
1042 fn integer_value_test() {
1043 let result = parse("0xA".as_ref());
1044 let integer = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap().as_integer_node().unwrap().value();
1045 let value: i32 = integer.try_into().unwrap();
1046
1047 assert_eq!(value, 10);
1048 }
1049
1050 #[test]
1051 fn integer_small_value_to_u32_digits_test() {
1052 let result = parse("0xA".as_ref());
1053 let integer = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap().as_integer_node().unwrap().value();
1054 let (negative, digits) = integer.to_u32_digits();
1055
1056 assert!(!negative);
1057 assert_eq!(digits, &[10]);
1058 }
1059
1060 #[test]
1061 fn integer_large_value_to_u32_digits_test() {
1062 let result = parse("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".as_ref());
1063 let integer = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap().as_integer_node().unwrap().value();
1064 let (negative, digits) = integer.to_u32_digits();
1065
1066 assert!(!negative);
1067 assert_eq!(digits, &[4_294_967_295, 4_294_967_295, 4_294_967_295, 2_147_483_647]);
1068 }
1069
1070 #[test]
1071 fn float_value_test() {
1072 let result = parse("1.0".as_ref());
1073 let value: f64 = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap().as_float_node().unwrap().value();
1074
1075 assert!((value - 1.0).abs() < f64::EPSILON);
1076 }
1077
1078 #[test]
1079 fn regex_value_test() {
1080 let result = parse(b"//");
1081 let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap().as_regular_expression_node().unwrap();
1082 assert_eq!(node.unescaped(), b"");
1083 }
1084
1085 #[test]
1086 fn node_field_lifetime_test() {
1087 #![allow(clippy::needless_pass_by_value)]
1090
1091 use crate::Node;
1092
1093 #[derive(Default)]
1094 struct Extract<'pr> {
1095 scopes: Vec<crate::ConstantId<'pr>>,
1096 }
1097
1098 impl<'pr> Extract<'pr> {
1099 fn push_scope(&mut self, path: Node<'pr>) {
1100 if let Some(cread) = path.as_constant_read_node() {
1101 self.scopes.push(cread.name());
1102 } else if let Some(cpath) = path.as_constant_path_node() {
1103 if let Some(parent) = cpath.parent() {
1104 self.push_scope(parent);
1105 }
1106 self.scopes.push(cpath.name().unwrap());
1107 } else {
1108 panic!("Wrong node kind!");
1109 }
1110 }
1111 }
1112
1113 let source = "Some::Random::Constant";
1114 let result = parse(source.as_ref());
1115 let node = result.node();
1116 let mut extractor = Extract::default();
1117 extractor.push_scope(node.as_program_node().unwrap().statements().body().iter().next().unwrap());
1118 assert_eq!(3, extractor.scopes.len());
1119 }
1120
1121 #[test]
1122 fn parse_with_options_frozen_string_literal_test() {
1123 use super::{parse_with_options, Options};
1124
1125 let source = b"\"foo\"";
1126 let options = Options::default().frozen_string_literal(Some(true)).build();
1127 let result = parse_with_options(source, &options);
1128
1129 let node = result.node();
1130 let string = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
1131 let string = string.as_string_node().unwrap();
1132 assert!(string.is_frozen());
1133 }
1134
1135 #[test]
1136 fn parse_with_options_filepath_test() {
1137 use super::{parse_with_options, Options};
1138
1139 let source = b"__FILE__";
1140 let options = Options::default().filepath("test.rb").build();
1141 let result = parse_with_options(source, &options);
1142 assert!(result.errors().next().is_none());
1143 }
1144
1145 #[test]
1146 fn parse_with_options_line_test() {
1147 use super::{parse_with_options, Options};
1148
1149 let source = b"1 + 2";
1150 let options = Options::default().line(10).build();
1151 let result = parse_with_options(source, &options);
1152 assert!(result.errors().next().is_none());
1153 }
1154
1155 #[test]
1156 #[allow(deprecated)]
1157 fn parse_with_options_cruby4_0_test() {
1158 use super::{parse_with_options, Options, Version};
1159
1160 let source = b"private def foo = puts 'Hello'";
1161 let newer_source = b"def foo(&nil); end";
1162
1163 for version in [Version::CRuby4_0, Version::CRuby3_5] {
1164 let options = Options::default().version(version).build();
1165 let result = parse_with_options(source, &options);
1166 assert!(result.errors().next().is_none());
1167
1168 let call = result.node().as_program_node().unwrap().statements().body().first().unwrap().as_call_node().unwrap();
1169 assert_eq!(call.name().as_slice(), b"private");
1170 let method = call.arguments().unwrap().arguments().first().unwrap().as_def_node().unwrap();
1171 assert_eq!(method.name().as_slice(), b"foo");
1172
1173 assert!(parse_with_options(newer_source, &options).errors().next().is_some());
1174 }
1175
1176 let earlier_options = Options::default().version(Version::CRuby3_4).build();
1177 assert!(parse_with_options(source, &earlier_options).errors().next().is_some());
1178
1179 let later_options = Options::default().version(Version::CRuby4_1).build();
1180 assert!(parse_with_options(newer_source, &later_options).errors().next().is_none());
1181 }
1182
1183 #[test]
1184 fn parse_with_options_default_line_test() {
1185 use super::{parse_with_options, Options};
1186
1187 let source = b"foo\nbar";
1188 let options = Options::default().filepath("test.rb").build();
1189 let result = parse_with_options(source, &options);
1190
1191 let node = result.node();
1192 let statements: Vec<_> = node.as_program_node().unwrap().statements().body().iter().collect();
1193 assert_eq!(statements[0].location().start_line(), 1);
1194 assert_eq!(statements[1].location().start_line(), 2);
1195 }
1196
1197 #[test]
1198 fn parse_with_options_scopes_test() {
1199 use super::{parse_with_options, Options, Scope};
1200
1201 let source = b"x";
1202 let scope = Scope::default().locals(vec![b"x".to_vec()]);
1203 let options = Options::default().scope(scope).build();
1204 let result = parse_with_options(source, &options);
1205 assert!(result.errors().next().is_none());
1206
1207 let node = result.node();
1208 let stmt = node.as_program_node().unwrap().statements().body().iter().next().unwrap();
1209 assert!(stmt.as_local_variable_read_node().is_some());
1210 }
1211
1212 #[test]
1213 fn diagnostic_message_test() {
1214 let source = b"# encoding: shift_jis\nx = <<~\x82\xA0\n";
1215 let result = parse(source);
1216 let error = result.errors().next().unwrap();
1217
1218 let message = error.message();
1219 assert!(std::str::from_utf8(message).is_err());
1220 assert!(message.windows(2).any(|window| window == b"\x82\xA0"));
1221 }
1222
1223 #[test]
1224 fn malformed_shebang() {
1225 let source = "#!\x00";
1226 let result = parse(source.as_ref());
1227 assert!(result.errors().next().is_none());
1228 assert!(result.warnings().next().is_none());
1229 }
1230}