Skip to main content

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;
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/// The version of Ruby syntax to parse with.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Version {
40    /// Use the latest version of prism.
41    Latest,
42    /// The vendored version of prism in `CRuby` 3.3.x.
43    CRuby3_3,
44    /// The vendored version of prism in `CRuby` 3.4.x.
45    CRuby3_4,
46    /// The vendored version of prism in `CRuby` 4.0.x, formerly known as 3.5.x.
47    #[deprecated(note = "Use Version::CRuby4_0 instead")]
48    CRuby3_5,
49    /// The vendored version of prism in `CRuby` 4.0.x.
50    CRuby4_0,
51    /// The vendored version of prism in `CRuby` 4.1.x.
52    CRuby4_1,
53}
54
55impl Version {
56    /// Calls `pm_options_version_set` with the appropriate version string.
57    /// `Latest` passes `NULL` to get the default behavior.
58    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/// A command line option that affects parsing behavior.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum CommandLineFlag {
83    /// `-a`: splits the input line `$_` into `$F`.
84    A,
85    /// `-e`: specifies a script to be executed.
86    E,
87    /// `-l`: chomps the input line by default.
88    L,
89    /// `-n`: wraps the script in a `while gets` loop.
90    N,
91    /// `-p`: prints the value of `$_` at the end of each loop.
92    P,
93    /// `-x`: searches the input file for a shebang.
94    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/// A forwarding parameter for a scope.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub enum ScopeForwardingFlag {
113    /// Forwarding with the `*` parameter.
114    Positionals,
115    /// Forwarding with the `**` parameter.
116    Keywords,
117    /// Forwarding with the `&` parameter.
118    Block,
119    /// Forwarding with the `...` parameter.
120    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/// A scope of locals surrounding the code that is being parsed.
135#[derive(Debug, Clone, Default)]
136pub struct Scope {
137    locals: Vec<Vec<u8>>,
138    forwarding: Vec<ScopeForwardingFlag>,
139}
140
141impl Scope {
142    /// Sets the local variable names in this scope.
143    #[must_use]
144    pub fn locals(mut self, locals: Vec<Vec<u8>>) -> Self {
145        self.locals = locals;
146        self
147    }
148
149    /// Sets the forwarding parameters in this scope.
150    #[must_use]
151    pub fn forwarding(mut self, forwarding: Vec<ScopeForwardingFlag>) -> Self {
152        self.forwarding = forwarding;
153        self
154    }
155}
156
157/// Options that can be passed to the parser.
158#[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    /// Sets the filepath option.
174    #[must_use]
175    pub fn filepath(mut self, filepath: &str) -> Self {
176        self.filepath = Some(filepath.to_string());
177        self
178    }
179
180    /// Sets the line option.
181    #[must_use]
182    pub const fn line(mut self, line: i32) -> Self {
183        self.line = Some(line);
184        self
185    }
186
187    /// Sets the encoding option.
188    #[must_use]
189    pub fn encoding(mut self, encoding: &str) -> Self {
190        self.encoding = Some(encoding.to_string());
191        self
192    }
193
194    /// Sets the encoding locked option.
195    #[must_use]
196    pub const fn encoding_locked(mut self, locked: bool) -> Self {
197        self.encoding_locked = locked;
198        self
199    }
200
201    /// Sets the frozen string literal option. `Some(true)` freezes string
202    /// literals, `Some(false)` keeps them mutable, and `None` leaves the
203    /// option unset.
204    #[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    /// Sets the command line flags.
211    #[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    /// Sets the version option.
218    #[must_use]
219    pub const fn version(mut self, version: Version) -> Self {
220        self.version = Some(version);
221        self
222    }
223
224    /// Sets the main script option.
225    #[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    /// Sets the partial script option.
232    #[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    /// Adds a scope to the options.
239    #[must_use]
240    pub fn scope(mut self, scope: Scope) -> Self {
241        self.scopes.push(scope);
242        self
243    }
244
245    /// Sets the scopes, replacing any previously added scopes.
246    #[must_use]
247    pub fn scopes(mut self, scopes: Vec<Scope>) -> Self {
248        self.scopes = scopes;
249        self
250    }
251
252    /// Builds the C-level parse options from these options. The returned
253    /// `ParseOptions` must outlive any `ParseResult` created from it.
254    ///
255    /// # Panics
256    ///
257    /// Panics if `filepath` or `encoding` contain interior null bytes.
258    #[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
331/// The C-level parse options. Created from [`Options::build`]. Must outlive
332/// any [`ParseResult`] created with [`parse_with_options`].
333pub struct ParseOptions {
334    options: *mut pm_options_t,
335    // These CStrings back the pm_string_t values inside `options`, which only
336    // view their bytes.
337    // They must not be dropped before `options` is freed.
338    _filepath: Option<CString>,
339    _encoding: Option<CString>,
340    // The scopes data that pm_string_t values point into.
341    _scopes: Vec<Scope>,
342}
343
344impl Drop for ParseOptions {
345    fn drop(&mut self) {
346        unsafe { pm_options_free(self.options) };
347    }
348}
349
350/// Initializes a parser, parses the source, and returns the result.
351///
352/// # Safety
353///
354/// `options` must be a valid pointer to a `pm_options_t` or null.
355unsafe 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/// Parses the given source string and returns a parse result.
363///
364/// # Panics
365///
366/// Panics if the parser fails to initialize.
367///
368#[must_use]
369pub fn parse(source: &[u8]) -> ParseResult<'_> {
370    unsafe { parse_impl(source, std::ptr::null()) }
371}
372
373/// Parses the given source string with the given options and returns a parse
374/// result. The `options` must outlive the returned `ParseResult`.
375///
376/// # Panics
377///
378/// Panics if the parser fails to initialize.
379///
380#[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        // Check that we don't go negative.
554        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        // The code below wouldn't typecheck prior to https://github.com/ruby/prism/pull/2519,
1088        // but we need to stop clippy from complaining about it.
1089        #![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}