Skip to main content

ParsedJS

Struct ParsedJS 

Source
pub struct ParsedJS { /* private fields */ }
Expand description

A successful parse: the AST arena, the source manager, and the Program node, owned together.

The AST is only valid while its arena is alive, so this value owns the arena; dropping it frees the AST. Read the tree with with_program, or dump it with to_estree_json.

Not Send. The arena uses Cell/UnsafeCell and the GCLock that guards it is thread-local by design, so a ParsedJS cannot be moved to another thread — parse on the thread that will read the AST. (The name keeps the crate’s JSParserImpl/JSLexer casing rather than Rust’s ParsedJs; that is deliberate, for consistency inside the port.)

Implementations§

Source§

impl ParsedJS

Source

pub fn with_program<R, F>(&mut self, f: F) -> R
where F: for<'gc> FnOnce(&'gc GCLock<'static, '_>, &'gc Node<'gc>) -> R,

Run f with the arena locked and the Program node in hand.

This is the read path for the AST: walk it with an hermes_ast::visitor::Visitor, match on Node arms, or read Node::kind. References into the arena cannot escape the closure — their lifetime ends with the lock — so return owned data instead. The one thing that can escape is an hermes_ast::context::NodeRc, which is refcounted rather than borrowed; dropping this ParsedJS while such a handle is still alive panics inside Context::drop.

The bound is higher-ranked because Node is invariant in its lifetime: a walker (hermes_ast::visitor::Visitor<'gc>) needs the node reference and the node’s own lifetime to be the same 'gc, which only a for<'gc> closure can promise.

§Why &mut self for a read

Reading the AST takes the arena lock, and Context::lock takes &mut self: the GCLock holds a &mut Context, which is what stops Context::gc — the mark-and-sweep that would invalidate every outstanding &Node — from running while the tree is being read. So &mut here means “exclusive view of the arena”, not “the AST is modified”; nothing in this method writes to the tree. Consequently a ParsedJS cannot be read through a shared reference: to share a parse, collect what you need inside the closure and hand out the owned result.

§Panics

Panics if another GCLock is active on this thread — in particular if with_program is called from inside another with_program.

Examples found in repository?
examples/walk_ast.rs (lines 58-64)
52fn main() {
53    let flags = ParseFlags::default();
54    let mut parsed = parse(SOURCE, flags).expect("snippet must parse");
55
56    // The AST is only reachable while the arena is locked, so collect owned
57    // data inside the closure and return it.
58    let counts = parsed.with_program(|_gc, program| {
59        let mut hist = Histogram {
60            counts: HashMap::new(),
61        };
62        hist.visit_node(program);
63        hist.counts
64    });
65
66    let mut rows: Vec<(NodeKind, usize)> = counts.into_iter().collect();
67    // Most frequent first; ties broken by kind name for a stable listing.
68    rows.sort_by(|a, b| {
69        b.1.cmp(&a.1)
70            .then_with(|| format!("{:?}", a.0).cmp(&format!("{:?}", b.0)))
71    });
72
73    let total: usize = rows.iter().map(|(_, n)| n).sum();
74    println!("{total} nodes, {} distinct kinds", rows.len());
75    for (kind, n) in rows {
76        println!("{:<24} {:>3} {}", format!("{kind:?}"), n, "#".repeat(n));
77    }
78}
Source

pub fn transform_program<R, F>(&mut self, f: F) -> R
where F: for<'gc> FnOnce(&'gc GCLock<'static, '_>, &'gc Node<'gc>, &mut SourceErrorManager) -> (&'gc Node<'gc>, R),

Run f with the arena locked, the Program node, and the source manager, then adopt the node f returns as this ParsedJS’s program.

This is with_program for a pass that rewrites the tree. A transforming visitor cannot mutate a node in place — the arena hands out shared references — so it rebuilds the ancestors of whatever it rewrote and returns a new root; that root is the one carrying the pass’s results, and keeping the old one would silently read a stale tree. Returning it from the closure is how the new root gets re-pinned here, in the one place that can do it without dropping the arena or leaving the pin dangling: the old pin is released only after the new one exists, and the arena is never touched in between. Returning the node f was given is fine and means “unchanged”.

The &mut SourceErrorManager is the pass’s diagnostic sink, and it is the same one the parse used: a pass reports through it, and the messages join the parse’s in diagnostics.

This exists for hermes-sema’s resolve façade, which is a transforming visitor over exactly this shape; nothing about it is specific to that crate.

The escape rules of with_program apply unchanged: references into the arena cannot leave the closure (the bound is higher-ranked for the same reason), while an hermes_ast::context::NodeRc can — and dropping this ParsedJS while one is alive panics inside Context::drop.

§Panics

Panics if another GCLock is active on this thread — in particular if this is called from inside with_program or from inside another transform_program.

Source

pub fn to_estree_json(&mut self, pretty: bool) -> String

Dump the AST as ESTree JSON: empty fields hidden, no loc/range, and "raw" source text on numeric literals (the only node the dumper emits it for). That is hermesc -dump-ast plus -include-raw-ast-prop.

pretty selects indented output. For other dumper settings use to_estree_json_with.

Takes &mut self although it only reads the tree, for the reason with_program documents: dumping locks the arena, and taking the lock needs exclusive access to the Context.

§Panics

Takes the arena lock, so it panics if another GCLock is live on this thread — in particular when called from inside with_program.

Examples found in repository?
examples/parse_to_estree_json.rs (line 46)
21fn main() {
22    let path = std::env::args().nth(1);
23    let (name, source) = match &path {
24        Some(p) => (
25            p.as_str(),
26            std::fs::read_to_string(p).unwrap_or_else(|e| {
27                eprintln!("cannot read '{p}': {e}");
28                std::process::exit(1);
29            }),
30        ),
31        None => (
32            "<builtin>",
33            "function greet(name) { return 'Hello, ' + name; }".to_string(),
34        ),
35    };
36
37    // Plain ECMAScript. A file extension says nothing about the dialect, so
38    // like `hermesc` this example assumes none; set the flags explicitly for
39    // the others, e.g.:
40    //     ParseFlags { parse_flow: true, ..Default::default() }   // Flow
41    //     ParseFlags { parse_ts: true, ..Default::default() }     // TypeScript
42    //     ParseFlags { parse_jsx: true, ..Default::default() }    // JSX
43    let flags = ParseFlags::default();
44
45    match parse_named(&source, name, flags) {
46        Ok(mut parsed) => print!("{}", parsed.to_estree_json(true)),
47        Err(e) => {
48            // `Display` is the one-line summary; `messages()` is the full
49            // LLVM-style rendering, which is what a CLI wants. Each string is
50            // already newline-terminated, so this is `eprint!`, not
51            // `eprintln!`.
52            for m in e.messages() {
53                eprint!("{m}");
54            }
55            std::process::exit(1);
56        }
57    }
58}
Source

pub fn to_estree_json_with( &mut self, pretty: bool, mode: ESTreeDumpMode, loc_mode: LocationDumpMode, raw_prop: ESTreeRawProp, ) -> String

Dump the AST as ESTree JSON with full control over the dumper, which is hermes_ast::dump::dump_estree_json_with_sm — see it for what each argument does.

&mut self for the same reason as to_estree_json: it takes the arena lock.

§Panics

Takes the arena lock, so it panics if another GCLock is live on this thread — in particular when called from inside with_program.

Source

pub fn diagnostics(&self) -> &[ResolvedDiagnostic]

The diagnostics recorded while parsing.

An Ok parse reported no errors, so these are warnings and notes. Render one with hermes_support::render::render_diagnostic.

Source

pub fn source_manager(&self) -> &SourceErrorManager

The source manager that owns the parsed buffer, for coordinate lookups (find_coords) and for driving the ast dumper by hand.

Trait Implementations§

Source§

impl Debug for ParsedJS

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Summarizes the arena rather than printing the AST, which can be huge. (Hand-written because SourceErrorManager is not Debug.)

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.