Skip to main content

resolve_and_dump/
resolve_and_dump.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8//! Parse a JavaScript file, resolve it, and print what came out.
9//!
10//! ```text
11//! cargo run -p hermes-sema --example resolve_and_dump -- file.js
12//! cargo run -p hermes-sema --example resolve_and_dump -- --summary file.js
13//! ```
14//!
15//! The default output is the `hermesc -dump-sema` text — the `SemContext`
16//! followed by the AST annotated with each identifier's resolution — which is
17//! the same dump this crate's differential gate compares byte-for-byte.
18//! `--summary` prints a short human-readable count instead, to show the
19//! `SemContext` being queried rather than dumped.
20//!
21//! This is the whole façade in ~20 lines of real code: `parse` (from
22//! `hermes-parser`), `resolve_for_compile` (this crate), then read the result.
23
24use std::io::Write;
25use std::process::ExitCode;
26
27use hermes_parser::{parse_named, ParseFlags};
28use hermes_sema::{resolve_for_compile, CompileOptions, ResolvedJS};
29
30fn main() -> ExitCode {
31    let mut args = std::env::args().skip(1);
32    let mut summary = false;
33    let mut path = None;
34    for arg in args.by_ref() {
35        match arg.as_str() {
36            "--summary" => summary = true,
37            _ => path = Some(arg),
38        }
39    }
40    let Some(path) = path else {
41        eprintln!("usage: resolve_and_dump [--summary] <file.js>");
42        return ExitCode::from(1);
43    };
44
45    let source = match std::fs::read_to_string(&path) {
46        Ok(s) => s,
47        Err(e) => {
48            eprintln!("resolve_and_dump: cannot read '{path}': {e}");
49            return ExitCode::from(1);
50        }
51    };
52
53    // Step 1: parse. `ParseFlags::default()` is plain ECMAScript; set
54    // `parse_flow`, `parse_ts` or `parse_jsx` for the other dialects.
55    let parsed = match parse_named(&source, &path, ParseFlags::default()) {
56        Ok(parsed) => parsed,
57        Err(e) => {
58            for m in e.messages() {
59                eprint!("{m}");
60            }
61            return ExitCode::from(2);
62        }
63    };
64
65    // Step 2: resolve. The compile path, with the standard globals declared —
66    // what `hermesc -dump-sema` does. `hermes_sema::resolve` is the parser
67    // path instead: no ambient declarations and no AST rewrites.
68    let options = CompileOptions::default();
69    let mut resolved = match resolve_for_compile(parsed, &options) {
70        Ok(resolved) => resolved,
71        Err(e) => {
72            for m in e.messages() {
73                eprint!("{m}");
74            }
75            return ExitCode::from(2);
76        }
77    };
78
79    // Warnings, if any: resolution succeeded, so none of these is an error.
80    for d in resolved.diagnostics() {
81        eprintln!("{}:{}:{}: {}", d.file_name, d.line, d.col, d.message);
82    }
83
84    // Step 3: read the result.
85    if summary {
86        print_summary(&mut resolved);
87    } else {
88        // Bytes, not a `String`: an identifier can be an unpaired surrogate,
89        // which the dumper writes as WTF-8.
90        let dump = resolved.to_sema_dump();
91        std::io::stdout().write_all(&dump).expect("write failed");
92    }
93    ExitCode::SUCCESS
94}
95
96/// Walk the tree and count what resolution decided, using the `SemContext`
97/// the way a consumer would.
98fn print_summary(resolved: &mut ResolvedJS) {
99    use hermes_parser::ast::node::Node;
100    use hermes_parser::ast::visitor::Visitor;
101
102    /// Counts identifier *expressions* by whether they resolved.
103    struct Counter<'a> {
104        sem: &'a hermes_sema::sem_context::SemContext,
105        resolved: usize,
106        unresolved: usize,
107    }
108    impl<'gc> Visitor<'gc> for Counter<'_> {
109        fn visit_node(&mut self, node: &'gc Node<'gc>) {
110            if let Node::Identifier(id) = node {
111                match self.sem.get_expression_decl(id) {
112                    Some(_) => self.resolved += 1,
113                    None => self.unresolved += 1,
114                }
115            }
116            node.visit_children(self);
117        }
118    }
119
120    let (resolved_ids, unresolved_ids) =
121        resolved.with_program(|_gc, root, sem| {
122            let mut c = Counter {
123                sem,
124                resolved: 0,
125                unresolved: 0,
126            };
127            c.visit_node(root);
128            (c.resolved, c.unresolved)
129        });
130
131    let sem = resolved.sem_context();
132    println!("functions:            {}", sem.functions_len());
133    println!("resolved references:  {resolved_ids}");
134    println!("other identifiers:    {unresolved_ids}");
135}