use super::super::{
emit_using_alias_assigns, extract_catch_param, extract_return_value_flow_with_handler,
extract_return_value_kind_with_handler, extract_return_value_name_with_handler,
extract_return_value_text, extract_throw_value_name, extract_yield_value_flow_with_handler,
looks_like_bare_identifier, node_text, span_of, FlowEvent, Node,
};
use super::{walk_into, LoweringContext};
pub(super) fn lower_control_and_scope(
node: Node<'_>,
context: LoweringContext<'_>,
out: &mut Vec<FlowEvent>,
) -> bool {
let LoweringContext {
file,
src,
handler,
class_names,
} = context;
let kind = node.kind();
if handler.is_break(kind) {
let label = handler
.control_label_field_names
.iter()
.find_map(|field| node.child_by_field_name(field))
.map(|n| node_text(&n, src).trim().to_string())
.filter(|s| !s.is_empty());
out.push(FlowEvent::Break {
span: span_of(file, &node),
label,
});
return true;
}
if handler.is_continue(kind) {
let label = handler
.control_label_field_names
.iter()
.find_map(|field| node.child_by_field_name(field))
.map(|n| node_text(&n, src).trim().to_string())
.filter(|s| !s.is_empty());
out.push(FlowEvent::Continue {
span: span_of(file, &node),
label,
});
return true;
}
if handler.is_yield(kind) {
let value_node = handler
.yield_value_field_names
.iter()
.find_map(|field| node.child_by_field_name(field))
.or_else(|| {
let mut cursor = node.walk();
let value = node.named_children(&mut cursor).next();
value
});
let value_text = value_node
.map(|value| node_text(&value, src).trim().to_string())
.filter(|value| !value.is_empty());
out.push(FlowEvent::Yield {
span: span_of(file, &node),
value_text,
value_flow: extract_yield_value_flow_with_handler(&node, file, src, handler),
});
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
walk_into(child, file, src, handler, class_names, out, false);
}
return true;
}
if handler.is_await(kind) {
let value_name = {
let mut cursor = node.walk();
let first_child = node.named_children(&mut cursor).next();
first_child.and_then(|child| {
let child_text = node_text(&child, src).trim();
if looks_like_bare_identifier(child_text) {
Some(child_text.to_string())
} else {
None
}
})
};
out.push(FlowEvent::Await {
span: span_of(file, &node),
value_name,
});
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
walk_into(child, file, src, handler, class_names, out, false);
}
return true;
}
if handler.is_defer(kind) {
let mut body = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
walk_into(child, file, src, handler, class_names, &mut body, false);
}
out.push(FlowEvent::Defer {
span: span_of(file, &node),
body,
});
return true;
}
if handler.is_using(kind) {
let body_node = handler
.using_body_field_names
.iter()
.find_map(|field| node.child_by_field_name(field));
let mut body = Vec::new();
if let Some(b) = body_node {
walk_into(b, file, src, handler, class_names, &mut body, false);
let body_id = b.id();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.id() == body_id {
continue;
}
emit_using_alias_assigns(child, file, src, handler, out);
walk_into(child, file, src, handler, class_names, out, false);
}
} else {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
emit_using_alias_assigns(child, file, src, handler, &mut body);
walk_into(child, file, src, handler, class_names, &mut body, false);
}
}
out.push(FlowEvent::Using {
span: span_of(file, &node),
body,
});
return true;
}
false
}
pub(super) fn lower_try(node: Node<'_>, context: LoweringContext<'_>, out: &mut Vec<FlowEvent>) -> bool {
let LoweringContext {
file,
src,
handler,
class_names,
} = context;
let kind = node.kind();
if handler.is_try(kind) {
let mut body = Vec::new();
let mut catch_events = Vec::new();
let mut finally_events = Vec::new();
let body_node = handler
.try_body_field_names
.iter()
.find_map(|field| node.child_by_field_name(field));
let body_id = body_node.map(|n| n.id());
let mut pre_body_child_ids = Vec::new();
if let Some(b) = body_node {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.id() == b.id() {
break;
}
let ck = child.kind();
if handler.is_catch(ck) || handler.is_finally(ck) {
continue;
}
walk_into(child, file, src, handler, class_names, &mut body, false);
pre_body_child_ids.push(child.id());
}
walk_into(b, file, src, handler, class_names, &mut body, false);
}
let mut cursor = node.walk();
let mut saw_catch_kind = false;
let mut saw_finally_kind = false;
let mut block_children: Vec<Node<'_>> = Vec::new();
let mut prev_was_catch_marker = false;
for child in node.named_children(&mut cursor) {
if pre_body_child_ids.contains(&child.id()) {
prev_was_catch_marker = false;
continue;
}
let ck = child.kind();
if handler.is_catch(ck) {
saw_catch_kind = true;
walk_into(child, file, src, handler, class_names, &mut catch_events, false);
prev_was_catch_marker = true;
continue;
} else if handler.is_finally(ck) {
saw_finally_kind = true;
walk_into(child, file, src, handler, class_names, &mut finally_events, false);
prev_was_catch_marker = false;
continue;
} else if handler.catch_body_follows_marker
&& prev_was_catch_marker
&& handler.try_fallback_body_kinds.contains(&ck)
{
walk_into(child, file, src, handler, class_names, &mut catch_events, false);
prev_was_catch_marker = false;
continue;
} else if Some(child.id()) == body_id {
prev_was_catch_marker = false;
continue;
} else if body_node.is_none() {
if handler.try_fallback_body_kinds.contains(&ck) {
block_children.push(child);
} else {
walk_into(child, file, src, handler, class_names, &mut body, false);
}
} else if handler.try_fallback_body_kinds.contains(&ck) {
block_children.push(child);
} else {
walk_into(child, file, src, handler, class_names, &mut body, false);
}
prev_was_catch_marker = false;
}
if body_node.is_none() {
if let Some(first_block) = block_children.first() {
walk_into(*first_block, file, src, handler, class_names, &mut body, false);
}
}
if !saw_catch_kind && !saw_finally_kind && block_children.len() >= 2 {
let catch_block = &block_children[1];
walk_into(
*catch_block,
file,
src,
handler,
class_names,
&mut catch_events,
false,
);
}
out.push(FlowEvent::Try {
span: span_of(file, &node),
body,
catch_events,
finally_events,
catch_param: extract_catch_param(&node, src),
catch_types: Vec::new(),
});
return true;
}
false
}
pub(super) fn lower_function_exit(
node: Node<'_>,
context: LoweringContext<'_>,
out: &mut Vec<FlowEvent>,
) -> bool {
let LoweringContext {
file,
src,
handler,
class_names,
} = context;
let kind = node.kind();
if handler.is_return(kind) {
let leading = node_text(&node, src).trim_start();
if leading.starts_with("break") {
out.push(FlowEvent::Break {
span: span_of(file, &node),
label: None,
});
return true;
} else if leading.starts_with("continue") {
out.push(FlowEvent::Continue {
span: span_of(file, &node),
label: None,
});
return true;
}
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
walk_into(child, file, src, handler, class_names, out, false);
}
if leading.starts_with("throw") {
out.push(FlowEvent::Throw {
span: span_of(file, &node),
value_name: extract_throw_value_name(&node, src),
thrown_type: None,
});
} else {
out.push(FlowEvent::Return {
span: span_of(file, &node),
value_kind: extract_return_value_kind_with_handler(&node, src, handler),
value_text: extract_return_value_text(&node, src),
value_name: extract_return_value_name_with_handler(&node, src, handler),
value_flow: extract_return_value_flow_with_handler(&node, file, src, handler),
});
}
return true;
}
if handler.is_throw(kind) {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if !handler.is_assignment(child.kind()) {
walk_into(child, file, src, handler, class_names, out, false);
}
}
out.push(FlowEvent::Throw {
span: span_of(file, &node),
value_name: extract_throw_value_name(&node, src),
thrown_type: None,
});
return true;
}
false
}