ruby_prism/
lib.rs

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