1use std::collections::HashSet;
16
17use hermes_atom_table::{AtomBytes, AtomTable, INVALID_ATOM_BYTES};
18use hermes_support::json_emitter::JSONEmitter;
19use hermes_support::location::SMRange;
20use hermes_support::manager::SourceErrorManager;
21
22use crate::node::{Node, NodeKind};
23use crate::node_child::{NodeLabel, NodeList};
24
25#[derive(Clone, Copy, PartialEq, Eq, Debug)]
27pub enum ESTreeDumpMode {
28 Compact,
30 HideEmpty,
32 DumpAll,
34}
35
36#[derive(Clone, Copy, PartialEq, Eq, Debug)]
38pub enum LocationDumpMode {
39 None,
41 Loc,
43 Range,
45 LocAndRange,
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, Debug)]
51pub enum ESTreeRawProp {
52 Exclude,
54 Include,
57}
58
59const MAX_DEPTH: usize = 128;
61
62pub struct ESTreeJSONDumper<'a, 'w> {
65 json: &'a mut JSONEmitter<'w>,
66 atoms: &'a AtomTable,
67 sm: Option<&'a SourceErrorManager>,
68 mode: ESTreeDumpMode,
69 loc_mode: LocationDumpMode,
70 raw_prop: ESTreeRawProp,
71 include_source_locs: Option<&'a HashSet<NodeKind>>,
72 depth: usize,
73}
74
75impl<'a, 'w> ESTreeJSONDumper<'a, 'w> {
76 fn skip_empty(&self, is_empty: bool, ignore_if_empty: bool) -> bool {
78 if !is_empty {
79 return false;
80 }
81 match self.mode {
82 ESTreeDumpMode::Compact => true,
83 ESTreeDumpMode::HideEmpty => ignore_if_empty,
84 ESTreeDumpMode::DumpAll => false,
85 }
86 }
87
88 pub(crate) fn field_node<'n>(&mut self, key: &str, node: Option<&'n Node<'n>>, ignore: bool) {
91 if self.skip_empty(node.is_none(), ignore) {
92 return;
93 }
94 self.json.emit_key(key);
95 self.dump_node_ptr(node);
96 }
97
98 pub(crate) fn field_list<'n>(&mut self, key: &str, list: NodeList<'n>, ignore: bool) {
99 if self.skip_empty(list.is_empty(), ignore) {
100 return;
101 }
102 self.json.emit_key(key);
103 self.dump_node_list(list);
104 }
105
106 pub(crate) fn field_bool(&mut self, key: &str, val: bool, ignore: bool) {
107 if self.skip_empty(!val, ignore) {
109 return;
110 }
111 self.json.emit_key(key);
112 self.json.emit_bool(val);
113 }
114
115 pub(crate) fn field_number(&mut self, key: &str, val: f64, ignore: bool) {
116 if self.skip_empty(false, ignore) {
118 return;
119 }
120 self.json.emit_key(key);
121 self.json.emit_f64(val);
122 }
123
124 pub(crate) fn field_label(&mut self, key: &str, label: NodeLabel, ignore: bool) {
125 if self.skip_empty(false, ignore) {
127 return;
128 }
129 self.json.emit_key(key);
130 self.dump_label(label);
131 }
132
133 fn dump_node_ptr<'n>(&mut self, node: Option<&'n Node<'n>>) {
136 let node = match node {
137 Some(n) => n,
138 None => {
139 self.json.emit_null_value();
140 return;
141 }
142 };
143 self.depth += 1;
144 if self.depth > MAX_DEPTH {
145 self.json.emit_null_value();
151 self.depth -= 1;
152 return;
153 }
154 self.visit(node);
155 self.depth -= 1;
156 }
157
158 fn dump_node_list<'n>(&mut self, list: NodeList<'n>) {
159 self.json.open_array();
160 for n in list.iter() {
161 self.dump_node_ptr(Some(n));
162 }
163 self.json.close_array();
164 }
165
166 fn dump_label(&mut self, label: AtomBytes) {
167 if label == INVALID_ATOM_BYTES {
168 self.json.emit_null_value();
169 return;
170 }
171 let bytes = self.atoms.bytes(label);
172 let units = hermes_support::utf8::convert_utf8_with_surrogates_to_utf16(bytes);
173 self.json.emit_u16(&units);
174 }
175
176 fn visit<'n>(&mut self, node: &'n Node<'n>) {
179 self.json.open_dict();
180 self.json.emit_key("type");
181 self.json.emit_str(node.node_type_str());
182 node.dump_children(self);
183 if node.kind() == NodeKind::NumericLiteral && self.raw_prop == ESTreeRawProp::Include {
184 self.dump_raw(node);
185 }
186 self.print_source_location(node);
187 self.json.close_dict();
188 }
189
190 fn dump_raw<'n>(&mut self, node: &'n Node<'n>) {
194 let sm = match self.sm {
195 Some(sm) => sm,
196 None => return,
197 };
198 let r = node.range();
199 if !range_is_valid(r) {
200 return;
201 }
202 let buf = sm.find_buffer_for_loc(r.start);
203 let bytes = match buf.bytes().get(r.start.offset as usize..r.end.offset as usize) {
207 Some(b) => b,
208 None => return,
209 };
210 self.json.emit_key("raw");
211 let units = hermes_support::utf8::convert_utf8_with_surrogates_to_utf16(bytes);
214 self.json.emit_u16(&units);
215 }
216
217 fn print_source_location<'n>(&mut self, node: &'n Node<'n>) {
218 if self.loc_mode == LocationDumpMode::None {
219 return;
220 }
221 if let Some(set) = self.include_source_locs {
222 if !set.contains(&node.kind()) {
223 return;
224 }
225 }
226 let sm = match self.sm {
227 Some(sm) => sm,
228 None => return,
229 };
230 let r = node.range();
231 if !range_is_valid(r) {
232 return;
233 }
234 let buf = sm.find_buffer_for_loc(r.start);
240 let buf_len = buf.bytes().len();
241 if r.start.offset as usize > buf_len || r.end.offset as usize > buf_len {
242 return;
243 }
244 let start = sm.find_coords(r.start);
245 let end = sm.find_coords(r.end);
246
247 if matches!(
248 self.loc_mode,
249 LocationDumpMode::Loc | LocationDumpMode::LocAndRange
250 ) {
251 self.json.emit_key("loc");
252 self.json.open_dict();
253 self.json.emit_key("start");
254 self.json.open_dict();
255 self.json.emit_key("line");
256 self.json.emit_u64(start.line as u64);
257 self.json.emit_key("column");
258 self.json.emit_u64(start.col as u64);
259 self.json.close_dict();
260 self.json.emit_key("end");
261 self.json.open_dict();
262 self.json.emit_key("line");
263 self.json.emit_u64(end.line as u64);
264 self.json.emit_key("column");
265 self.json.emit_u64(end.col as u64);
266 self.json.close_dict();
267 self.json.close_dict();
268 }
269
270 if matches!(
271 self.loc_mode,
272 LocationDumpMode::Range | LocationDumpMode::LocAndRange
273 ) {
274 self.json.emit_key("range");
275 self.json.open_array();
276 dump_sm_range_json(self.json, r);
277 self.json.close_array();
278 }
279 }
280}
281
282fn range_is_valid(r: SMRange) -> bool {
286 r.start.source == r.end.source && r.start.offset <= r.end.offset
287}
288
289pub fn dump_sm_range_json(json: &mut JSONEmitter, rng: SMRange) {
294 json.emit_u64(rng.start.offset as u64);
295 json.emit_u64(rng.end.offset as u64);
296}
297
298pub fn dump_estree_json<'n>(
303 out: &mut String,
304 root: &'n Node<'n>,
305 pretty: bool,
306 mode: ESTreeDumpMode,
307 atoms: &AtomTable,
308) {
309 let mut json = JSONEmitter::new(out, pretty);
310 {
311 let mut d = ESTreeJSONDumper {
312 json: &mut json,
313 atoms,
314 sm: None,
315 mode,
316 loc_mode: LocationDumpMode::None,
317 raw_prop: ESTreeRawProp::Include,
318 include_source_locs: None,
319 depth: 0,
320 };
321 d.dump_node_ptr(Some(root));
322 }
323 json.end_jsonl();
324}
325
326#[allow(clippy::too_many_arguments)]
329pub fn dump_estree_json_with_sm<'n>(
330 out: &mut String,
331 root: &'n Node<'n>,
332 pretty: bool,
333 mode: ESTreeDumpMode,
334 sm: &SourceErrorManager,
335 loc_mode: LocationDumpMode,
336 raw_prop: ESTreeRawProp,
337 atoms: &AtomTable,
338) {
339 let mut json = JSONEmitter::new(out, pretty);
340 {
341 let mut d = ESTreeJSONDumper {
342 json: &mut json,
343 atoms,
344 sm: Some(sm),
345 mode,
346 loc_mode,
347 raw_prop,
348 include_source_locs: None,
349 depth: 0,
350 };
351 d.dump_node_ptr(Some(root));
352 }
353 json.end_jsonl();
354}