use std::collections::HashSet;
use hermes_atom_table::{AtomBytes, AtomTable, INVALID_ATOM_BYTES};
use hermes_support::json_emitter::JSONEmitter;
use hermes_support::location::SMRange;
use hermes_support::manager::SourceErrorManager;
use crate::node::{Node, NodeKind};
use crate::node_child::{NodeLabel, NodeList};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ESTreeDumpMode {
Compact,
HideEmpty,
DumpAll,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LocationDumpMode {
None,
Loc,
Range,
LocAndRange,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ESTreeRawProp {
Exclude,
Include,
}
const MAX_DEPTH: usize = 128;
pub struct ESTreeJSONDumper<'a, 'w> {
json: &'a mut JSONEmitter<'w>,
atoms: &'a AtomTable,
sm: Option<&'a SourceErrorManager>,
mode: ESTreeDumpMode,
loc_mode: LocationDumpMode,
raw_prop: ESTreeRawProp,
include_source_locs: Option<&'a HashSet<NodeKind>>,
depth: usize,
}
impl<'a, 'w> ESTreeJSONDumper<'a, 'w> {
fn skip_empty(&self, is_empty: bool, ignore_if_empty: bool) -> bool {
if !is_empty {
return false;
}
match self.mode {
ESTreeDumpMode::Compact => true,
ESTreeDumpMode::HideEmpty => ignore_if_empty,
ESTreeDumpMode::DumpAll => false,
}
}
pub(crate) fn field_node<'n>(&mut self, key: &str, node: Option<&'n Node<'n>>, ignore: bool) {
if self.skip_empty(node.is_none(), ignore) {
return;
}
self.json.emit_key(key);
self.dump_node_ptr(node);
}
pub(crate) fn field_list<'n>(&mut self, key: &str, list: NodeList<'n>, ignore: bool) {
if self.skip_empty(list.is_empty(), ignore) {
return;
}
self.json.emit_key(key);
self.dump_node_list(list);
}
pub(crate) fn field_bool(&mut self, key: &str, val: bool, ignore: bool) {
if self.skip_empty(!val, ignore) {
return;
}
self.json.emit_key(key);
self.json.emit_bool(val);
}
pub(crate) fn field_number(&mut self, key: &str, val: f64, ignore: bool) {
if self.skip_empty(false, ignore) {
return;
}
self.json.emit_key(key);
self.json.emit_f64(val);
}
pub(crate) fn field_label(&mut self, key: &str, label: NodeLabel, ignore: bool) {
if self.skip_empty(false, ignore) {
return;
}
self.json.emit_key(key);
self.dump_label(label);
}
fn dump_node_ptr<'n>(&mut self, node: Option<&'n Node<'n>>) {
let node = match node {
Some(n) => n,
None => {
self.json.emit_null_value();
return;
}
};
self.depth += 1;
if self.depth > MAX_DEPTH {
self.json.emit_null_value();
self.depth -= 1;
return;
}
self.visit(node);
self.depth -= 1;
}
fn dump_node_list<'n>(&mut self, list: NodeList<'n>) {
self.json.open_array();
for n in list.iter() {
self.dump_node_ptr(Some(n));
}
self.json.close_array();
}
fn dump_label(&mut self, label: AtomBytes) {
if label == INVALID_ATOM_BYTES {
self.json.emit_null_value();
return;
}
let bytes = self.atoms.bytes(label);
let units = hermes_support::utf8::convert_utf8_with_surrogates_to_utf16(bytes);
self.json.emit_u16(&units);
}
fn visit<'n>(&mut self, node: &'n Node<'n>) {
self.json.open_dict();
self.json.emit_key("type");
self.json.emit_str(node.node_type_str());
node.dump_children(self);
if node.kind() == NodeKind::NumericLiteral && self.raw_prop == ESTreeRawProp::Include {
self.dump_raw(node);
}
self.print_source_location(node);
self.json.close_dict();
}
fn dump_raw<'n>(&mut self, node: &'n Node<'n>) {
let sm = match self.sm {
Some(sm) => sm,
None => return,
};
let r = node.range();
if !range_is_valid(r) {
return;
}
let buf = sm.find_buffer_for_loc(r.start);
let bytes = match buf.bytes().get(r.start.offset as usize..r.end.offset as usize) {
Some(b) => b,
None => return,
};
self.json.emit_key("raw");
let units = hermes_support::utf8::convert_utf8_with_surrogates_to_utf16(bytes);
self.json.emit_u16(&units);
}
fn print_source_location<'n>(&mut self, node: &'n Node<'n>) {
if self.loc_mode == LocationDumpMode::None {
return;
}
if let Some(set) = self.include_source_locs {
if !set.contains(&node.kind()) {
return;
}
}
let sm = match self.sm {
Some(sm) => sm,
None => return,
};
let r = node.range();
if !range_is_valid(r) {
return;
}
let buf = sm.find_buffer_for_loc(r.start);
let buf_len = buf.bytes().len();
if r.start.offset as usize > buf_len || r.end.offset as usize > buf_len {
return;
}
let start = sm.find_coords(r.start);
let end = sm.find_coords(r.end);
if matches!(
self.loc_mode,
LocationDumpMode::Loc | LocationDumpMode::LocAndRange
) {
self.json.emit_key("loc");
self.json.open_dict();
self.json.emit_key("start");
self.json.open_dict();
self.json.emit_key("line");
self.json.emit_u64(start.line as u64);
self.json.emit_key("column");
self.json.emit_u64(start.col as u64);
self.json.close_dict();
self.json.emit_key("end");
self.json.open_dict();
self.json.emit_key("line");
self.json.emit_u64(end.line as u64);
self.json.emit_key("column");
self.json.emit_u64(end.col as u64);
self.json.close_dict();
self.json.close_dict();
}
if matches!(
self.loc_mode,
LocationDumpMode::Range | LocationDumpMode::LocAndRange
) {
self.json.emit_key("range");
self.json.open_array();
dump_sm_range_json(self.json, r);
self.json.close_array();
}
}
}
fn range_is_valid(r: SMRange) -> bool {
r.start.source == r.end.source && r.start.offset <= r.end.offset
}
pub fn dump_sm_range_json(json: &mut JSONEmitter, rng: SMRange) {
json.emit_u64(rng.start.offset as u64);
json.emit_u64(rng.end.offset as u64);
}
pub fn dump_estree_json<'n>(
out: &mut String,
root: &'n Node<'n>,
pretty: bool,
mode: ESTreeDumpMode,
atoms: &AtomTable,
) {
let mut json = JSONEmitter::new(out, pretty);
{
let mut d = ESTreeJSONDumper {
json: &mut json,
atoms,
sm: None,
mode,
loc_mode: LocationDumpMode::None,
raw_prop: ESTreeRawProp::Include,
include_source_locs: None,
depth: 0,
};
d.dump_node_ptr(Some(root));
}
json.end_jsonl();
}
#[allow(clippy::too_many_arguments)]
pub fn dump_estree_json_with_sm<'n>(
out: &mut String,
root: &'n Node<'n>,
pretty: bool,
mode: ESTreeDumpMode,
sm: &SourceErrorManager,
loc_mode: LocationDumpMode,
raw_prop: ESTreeRawProp,
atoms: &AtomTable,
) {
let mut json = JSONEmitter::new(out, pretty);
{
let mut d = ESTreeJSONDumper {
json: &mut json,
atoms,
sm: Some(sm),
mode,
loc_mode,
raw_prop,
include_source_locs: None,
depth: 0,
};
d.dump_node_ptr(Some(root));
}
json.end_jsonl();
}