use crate::models::{CallIntent, EntityKind, ParsedEntity, ReferenceIntent};
use crate::pipeline::parser::utils::{
extract_identifiers_from_decorator, extract_new_expression_name, is_capitalized, node_text,
};
use tree_sitter::Node;
use uuid::Uuid;
pub(crate) fn collect_all_reference_intents_javascript(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<(ReferenceIntent, usize)>,
) {
let byte_pos = node.start_byte();
let line = node.start_position().row + 1;
match node.kind() {
"call_expression" | "new_expression" => {
let call_intents = extract_single_call_intent_javascript(node, source);
for call in call_intents {
intents.push((
ReferenceIntent::Call {
method: call.method,
receiver: call.receiver,
line,
arg_count: call.arg_count,
},
byte_pos,
));
}
}
"jsx_self_closing_element" | "jsx_opening_element" => {
let mut call_intents = Vec::new();
extract_jsx_component_invocation(node, source, &mut call_intents);
for call in call_intents {
intents.push((
ReferenceIntent::Call {
method: call.method,
receiver: call.receiver,
line,
arg_count: call.arg_count,
},
byte_pos,
));
}
}
"decorator" => {
let mut decorator_refs = Vec::new();
extract_identifiers_from_decorator(node, source, &mut decorator_refs, line);
for ref_intent in decorator_refs {
intents.push((ref_intent, byte_pos));
}
}
"import_statement" => {
collect_import_intents_javascript(node, source, intents, byte_pos, line, false);
}
"lexical_declaration" | "variable_declaration" => {
collect_require_destructure_intents(node, source, intents, byte_pos, line);
}
_ => {}
}
let mut child = node.child(0);
while let Some(c) = child {
collect_all_reference_intents_javascript(c, source, intents);
child = c.next_sibling();
}
}
#[expect(
clippy::too_many_arguments,
reason = "function is verbose but correct — extraction deferred"
)]
pub(crate) fn collect_import_intents_javascript(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<(ReferenceIntent, usize)>,
byte_pos: usize,
line: usize,
is_type_import: bool,
) {
let mut cursor = node.walk();
let import_clause = node
.children(&mut cursor)
.find(|c| c.kind() == "import_clause");
let Some(clause) = import_clause else {
return;
};
let mut clause_child = clause.child(0);
while let Some(c) = clause_child {
match c.kind() {
"named_imports" => {
let mut spec_child = c.child(0);
while let Some(spec) = spec_child {
if spec.kind() == "import_specifier" {
let name_node = spec.child_by_field_name("name");
if let Some(nn) = name_node {
let name = node_text(nn, source);
push_import_ref_if_capitalized(
name,
is_type_import,
intents,
byte_pos,
line,
);
}
}
spec_child = spec.next_sibling();
}
}
"identifier" => {
let name = node_text(c, source);
push_import_ref_if_capitalized(name, is_type_import, intents, byte_pos, line);
}
_ => {}
}
clause_child = c.next_sibling();
}
}
fn push_import_ref_if_capitalized(
name: String,
is_type_import: bool,
intents: &mut Vec<(ReferenceIntent, usize)>,
byte_pos: usize,
line: usize,
) {
if is_capitalized(&name) {
if is_type_import {
intents.push((
ReferenceIntent::TypeReference {
type_name: name,
line,
},
byte_pos,
));
} else {
intents.push((
ReferenceIntent::ValueReference {
value_name: name,
line,
},
byte_pos,
));
}
}
}
fn collect_require_destructure_intents(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<(ReferenceIntent, usize)>,
byte_pos: usize,
line: usize,
) {
let mut declarator = None;
let mut child = node.child(0);
while let Some(c) = child {
if c.kind() == "variable_declarator" {
declarator = Some(c);
break;
}
child = c.next_sibling();
}
let Some(decl) = declarator else {
return;
};
let Some(value) = decl.child_by_field_name("value") else {
return;
};
if value.kind() != "call_expression" {
return;
}
let Some(func) = value.child_by_field_name("function") else {
return;
};
if func.kind() != "identifier" || node_text(func, source) != "require" {
return;
}
let Some(name_node) = decl.child_by_field_name("name") else {
return;
};
if name_node.kind() != "object_pattern" {
return;
}
let mut pattern_child = name_node.child(0);
while let Some(pc) = pattern_child {
match pc.kind() {
"shorthand_property_identifier_pattern" => {
let name = node_text(pc, source);
if is_capitalized(&name) {
intents.push((
ReferenceIntent::ValueReference {
value_name: name,
line,
},
byte_pos,
));
}
}
"pair_pattern" => {
if let Some(key) = pc.child_by_field_name("key") {
let name = node_text(key, source);
if is_capitalized(&name) {
intents.push((
ReferenceIntent::ValueReference {
value_name: name,
line,
},
byte_pos,
));
}
}
}
_ => {}
}
pattern_child = pc.next_sibling();
}
}
pub(crate) fn extract_class_inheritance_js(
class_node: Node<'_>,
source: &[u8],
intents: &mut Vec<ReferenceIntent>,
) {
let line = class_node.start_position().row + 1;
let mut child = class_node.child(0);
while let Some(c) = child {
if c.kind() == "class_heritage" {
let parent_name = extract_js_heritage_name(c, source);
if let Some(name) = parent_name {
intents.push(ReferenceIntent::Extends { parent: name, line });
}
}
child = c.next_sibling();
}
}
fn extract_js_heritage_name(node: Node<'_>, source: &[u8]) -> Option<String> {
let mut child = node.child(0);
while let Some(c) = child {
match c.kind() {
"identifier" => return Some(node_text(c, source)),
"member_expression" => {
return node_text(c, source)
.split('.')
.next_back()
.map(|s| s.to_string());
}
_ => {
if let Some(name) = extract_js_heritage_name(c, source) {
return Some(name);
}
}
}
child = c.next_sibling();
}
None
}
pub(crate) fn extract_reference_intents_javascript(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<ReferenceIntent>,
) {
let mut call_intents = Vec::new();
extract_call_intents_javascript(node, source, &mut call_intents);
for call in call_intents {
intents.push(ReferenceIntent::Call {
method: call.method,
receiver: call.receiver,
line: call.line,
arg_count: call.arg_count,
});
}
extract_enum_usages_javascript(node, source, intents);
}
pub(crate) fn extract_call_intents_javascript(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<CallIntent>,
) {
intents.extend(extract_single_call_intent_javascript(node, source));
let mut child = node.child(0);
while let Some(c) = child {
extract_call_intents_javascript(c, source, intents);
child = c.next_sibling();
}
}
#[expect(
clippy::cognitive_complexity,
reason = "function is verbose but correct — extraction deferred"
)]
pub(crate) fn extract_single_call_intent_javascript(
node: Node<'_>,
source: &[u8],
) -> Vec<CallIntent> {
let mut intents = Vec::new();
if node.kind() == "call_expression" {
let line = node.start_position().row + 1;
let mut method_name: Option<String> = None;
let mut receiver: Option<String> = None;
let mut child = node.child(0);
let mut is_bind_call = false;
while let Some(c) = child {
if c.kind() == "member_expression" {
if let Some(property_node) = c.child_by_field_name("property") {
let prop_text = node_text(property_node, source);
if prop_text == "bind" {
is_bind_call = true;
}
method_name = Some(prop_text);
}
if let Some(object_node) = c.child_by_field_name("object") {
receiver = Some(node_text(object_node, source));
}
} else if c.kind() == "identifier" {
method_name = Some(node_text(c, source));
}
child = c.next_sibling();
}
if let Some(method) = method_name {
if is_bind_call {
if let Some(receiver) = receiver
&& let Some(last_part) = receiver.split('.').next_back()
{
intents.push(CallIntent {
method: last_part.to_string(),
receiver: if receiver.contains('.') {
receiver.split('.').next().map(|s| s.to_string())
} else {
Some("this".to_string())
},
line,
arg_count: None,
});
}
} else {
intents.push(CallIntent {
method,
receiver,
line,
arg_count: None,
});
}
}
extract_callback_arguments(node, source, &mut intents, line);
} else if node.kind() == "new_expression" {
let line = node.start_position().row + 1;
if let Some(name) = extract_new_expression_name(node, source) {
intents.push(CallIntent {
method: name,
receiver: None,
line,
arg_count: None,
});
}
} else if node.kind() == "jsx_self_closing_element" || node.kind() == "jsx_opening_element" {
extract_jsx_component_invocation(node, source, &mut intents);
} else if node.kind() == "member_expression" {
if let Some(object_node) = node.child_by_field_name("object")
&& node_text(object_node, source) == "this"
&& let Some(property_node) = node.child_by_field_name("property")
{
let prop_text = node_text(property_node, source);
let line = node.start_position().row + 1;
intents.push(CallIntent {
method: prop_text,
receiver: Some("this".to_string()),
line,
arg_count: None,
});
}
}
intents
}
pub(crate) fn extract_jsx_component_invocation(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<CallIntent>,
) {
let line = node.start_position().row + 1;
if let Some(name_node) = node.child_by_field_name("name") {
let comp_name = node_text(name_node, source);
if is_capitalized(&comp_name) {
if comp_name.contains('.') {
let mut parts = comp_name.split('.');
let receiver = parts.next().map(|s| s.to_string());
let method = parts.collect::<Vec<_>>().join(".");
intents.push(CallIntent {
method,
receiver,
line,
arg_count: None,
});
} else {
intents.push(CallIntent {
method: comp_name,
receiver: None,
line,
arg_count: None,
});
}
}
}
}
pub(crate) fn extract_callback_arguments(
call_node: Node<'_>,
source: &[u8],
intents: &mut Vec<CallIntent>,
line: usize,
) {
if let Some(args_node) = call_node.child_by_field_name("arguments") {
let mut arg = args_node.child(0);
while let Some(a) = arg {
if a.kind() == "member_expression" {
if let Some(property_node) = a.child_by_field_name("property") {
let method_name = node_text(property_node, source);
if let Some(object_node) = a.child_by_field_name("object") {
let receiver = node_text(object_node, source);
intents.push(CallIntent {
method: method_name,
receiver: Some(receiver),
line,
arg_count: None,
});
}
}
} else if a.kind() == "identifier" {
let name = node_text(a, source);
if !is_reserved_keyword(&name)
&& name.chars().next().is_some_and(|c| c.is_alphabetic())
{
intents.push(CallIntent {
method: name,
receiver: None,
line,
arg_count: None,
});
}
}
arg = a.next_sibling();
}
}
}
pub(crate) fn is_reserved_keyword(word: &str) -> bool {
matches!(
word,
"true"
| "false"
| "null"
| "undefined"
| "this"
| "super"
| "import"
| "export"
| "from"
| "as"
| "async"
| "await"
| "yield"
| "return"
| "throw"
| "try"
| "catch"
| "finally"
| "if"
| "else"
| "for"
| "while"
| "do"
| "break"
| "continue"
| "switch"
| "case"
| "default"
| "const"
| "let"
| "var"
| "class"
| "function"
| "new"
| "delete"
| "typeof"
| "instanceof"
| "in"
| "of"
| "static"
| "interface"
| "enum"
| "type"
| "public"
| "private"
| "protected"
| "readonly"
| "abstract"
| "extends"
| "implements"
| "declare"
)
}
pub(crate) fn extract_enum_usages_javascript(
node: Node<'_>,
source: &[u8],
intents: &mut Vec<ReferenceIntent>,
) {
if node.kind() == "member_expression" {
if let Some(object_node) = node.child_by_field_name("object")
&& object_node.kind() == "identifier"
{
let obj_text = node_text(object_node, source);
if is_capitalized(&obj_text) {
let line = object_node.start_position().row + 1;
intents.push(ReferenceIntent::TypeReference {
type_name: obj_text,
line,
});
}
}
}
let mut child = node.child(0);
while let Some(c) = child {
extract_enum_usages_javascript(c, source, intents);
child = c.next_sibling();
}
}
pub(crate) fn extract_jsx_attributes(
node: Node<'_>,
source: &[u8],
) -> Vec<(String, String, usize)> {
use crate::pipeline::parser::utils::node_text;
let mut attributes = Vec::new();
let mut child = node.child(0);
while let Some(c) = child {
if c.kind() == "jsx_attribute" {
let line = c.start_position().row + 1;
let mut attr_name = String::new();
let mut attr_value = String::new();
let mut attr_child = c.child(0);
while let Some(ac) = attr_child {
if ac.kind() == "property_identifier" {
attr_name = node_text(ac, source);
} else if ac.kind() == "string" {
let raw = node_text(ac, source);
attr_value = raw.trim_matches(|c| c == '"' || c == '\'').to_string();
} else if ac.kind() == "jsx_expression" {
attr_child = ac.next_sibling();
continue;
}
attr_child = ac.next_sibling();
}
if (attr_name == "id" || attr_name == "className") && !attr_value.is_empty() {
attributes.push((attr_name, attr_value, line));
}
}
child = c.next_sibling();
}
attributes
}
pub(crate) fn handle_dom_css_capture(
cap_name: &str,
text: &str,
line: usize,
) -> Option<ReferenceIntent> {
match cap_name {
"dom.element_id" => {
let clean_id = text
.trim_start_matches('"')
.trim_start_matches('\'')
.trim_end_matches('"')
.trim_end_matches('\'')
.to_string();
Some(ReferenceIntent::DomElementReference {
element_id: clean_id,
line,
})
}
"css.class_name" | "css.class_assignment" => {
let clean_class = text
.trim_start_matches('"')
.trim_start_matches('\'')
.trim_end_matches('"')
.trim_end_matches('\'')
.to_string();
Some(ReferenceIntent::CssClassUsage {
class_name: clean_class,
line,
})
}
_ => None,
}
}
#[expect(
clippy::too_many_lines,
reason = "function is verbose but correct — extraction deferred"
)]
pub(crate) fn extract_jsx_html_attributes(
node: Node<'_>,
source: &[u8],
entities: &mut Vec<ParsedEntity>,
file_path: &str,
repo_name: &str,
) {
if matches!(
node.kind(),
"jsx_self_closing_element" | "jsx_opening_element"
) {
let attrs = extract_jsx_attributes(node, source);
let line = node.start_position().row + 1;
for (attr_name, attr_value, _) in attrs {
if attr_name == "id" {
entities.push(ParsedEntity {
uuid: Uuid::new_v4(),
name: attr_value.clone(),
kind: EntityKind::HtmlId,
fqn: format!("#{}", attr_value),
signature: None,
docstring: None,
inline_comments: Vec::new(),
decorators: Vec::new(),
language: "javascript".to_string(),
file_path: file_path.to_string(),
start_line: line,
end_line: line,
enclosing_class: None,
repo_name: repo_name.to_string(),
reference_intents: Vec::new(),
calls: Vec::new(),
relationships: Vec::new(),
embed_text: String::new(),
rust_attributes: None,
impl_trait: None,
impl_target: None,
generics: None,
lifetimes: None,
alias_module_path: None,
original_export_name: None,
enclosing_class_fqn: None,
default_export: None,
is_test_context: false,
});
} else if attr_name == "className" {
for class_name in attr_value.split_whitespace() {
if !class_name.is_empty() {
entities.push(ParsedEntity {
uuid: Uuid::new_v4(),
name: class_name.to_string(),
kind: EntityKind::HtmlClass,
fqn: format!(".{}", class_name),
signature: None,
docstring: None,
inline_comments: Vec::new(),
decorators: Vec::new(),
language: "javascript".to_string(),
file_path: file_path.to_string(),
start_line: line,
end_line: line,
enclosing_class: None,
repo_name: repo_name.to_string(),
reference_intents: Vec::new(),
calls: Vec::new(),
relationships: Vec::new(),
embed_text: String::new(),
rust_attributes: None,
impl_trait: None,
impl_target: None,
generics: None,
lifetimes: None,
alias_module_path: None,
original_export_name: None,
enclosing_class_fqn: None,
default_export: None,
is_test_context: false,
});
}
}
}
}
}
let mut child = node.child(0);
while let Some(c) = child {
extract_jsx_html_attributes(c, source, entities, file_path, repo_name);
child = c.next_sibling();
}
}
pub(crate) fn extract_require_module_path(node: Node<'_>, source: &[u8]) -> Option<String> {
let mut declarator = node;
if node.kind() == "lexical_declaration" || node.kind() == "variable_declaration" {
let mut child = node.child(0);
while let Some(c) = child {
if c.kind() == "variable_declarator" {
declarator = c;
break;
}
child = c.next_sibling();
}
}
let value = declarator.child_by_field_name("value")?;
if value.kind() == "new_expression" {
if let Some(constructor) = value.child_by_field_name("constructor") {
extract_require_string(constructor, source)
} else {
None
}
} else if value.kind() == "call_expression" {
extract_require_string(value, source)
} else {
None
}
}
fn extract_require_string(call_node: Node<'_>, source: &[u8]) -> Option<String> {
let func = call_node.child_by_field_name("function")?;
if func.kind() != "identifier" || node_text(func, source) != "require" {
return None;
}
let args = call_node.child_by_field_name("arguments")?;
let first_arg = args.child(1)?;
if first_arg.kind() != "string" {
return None;
}
let raw = node_text(first_arg, source);
Some(
raw.trim_matches(|c| c == '\'' || c == '"' || c == '`')
.to_string(),
)
}
pub(crate) fn scan_module_exports_target(root: Node<'_>, source: &[u8]) -> Option<String> {
fn walk(node: Node<'_>, source: &[u8]) -> Option<String> {
if node.kind() == "assignment_expression" {
let left = node.child_by_field_name("left")?;
if left.kind() == "member_expression"
&& let Some(obj) = left.child_by_field_name("object")
&& node_text(obj, source) == "module"
&& let Some(prop) = left.child_by_field_name("property")
&& node_text(prop, source) == "exports"
{
let right = node.child_by_field_name("right")?;
if right.kind() == "identifier" {
return Some(node_text(right, source));
}
}
}
let mut child = node.child(0);
while let Some(c) = child {
if let Some(result) = walk(c, source) {
return Some(result);
}
child = c.next_sibling();
}
None
}
walk(root, source)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::parser::test_utils::{
find_call_expression, find_new_expression, find_var_decl,
};
#[test]
fn test_is_reserved_keyword_true() {
assert!(is_reserved_keyword("true"));
assert!(is_reserved_keyword("false"));
assert!(is_reserved_keyword("class"));
assert!(is_reserved_keyword("function"));
assert!(is_reserved_keyword("async"));
assert!(is_reserved_keyword("await"));
}
#[test]
fn test_is_reserved_keyword_false() {
assert!(!is_reserved_keyword("myVar"));
assert!(!is_reserved_keyword("handler"));
assert!(!is_reserved_keyword("MyClass"));
assert!(!is_reserved_keyword("someFunction"));
}
#[test]
fn test_extract_jsx_component_invocation_simple() {
crate::pipeline::parser::test_utils::assert_jsx_component_invocation(
"function render() { return <ChartToolbar />; }",
crate::pipeline::parser::test_utils::parse_javascript_snippet,
"ChartToolbar",
None,
);
}
#[test]
fn test_extract_jsx_component_invocation_namespaced() {
crate::pipeline::parser::test_utils::assert_jsx_component_invocation(
"function render() { return <Sheet.Content />; }",
crate::pipeline::parser::test_utils::parse_javascript_snippet,
"Content",
Some("Sheet"),
);
}
#[test]
fn test_extract_single_call_intent_javascript_simple() {
let code = "function test() { method(); }";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
if let Some(call) = find_call_expression(tree.root_node()) {
let code_bytes = code.as_bytes();
let intents = extract_single_call_intent_javascript(call, code_bytes);
assert!(!intents.is_empty());
assert_eq!(intents[0].method, "method");
assert!(intents[0].receiver.is_none());
}
}
#[test]
fn test_extract_single_call_intent_javascript_member() {
let code = "function test() { obj.method(); }";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
if let Some(call) = find_call_expression(tree.root_node()) {
let code_bytes = code.as_bytes();
let intents = extract_single_call_intent_javascript(call, code_bytes);
assert!(!intents.is_empty());
assert_eq!(intents[0].method, "method");
assert_eq!(intents[0].receiver, Some("obj".to_string()));
}
}
#[test]
fn test_extract_single_call_intent_javascript_new() {
let code = "function test() { new MyClass(); }";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
if let Some(new_expr) = find_new_expression(tree.root_node()) {
let code_bytes = code.as_bytes();
let intents = extract_single_call_intent_javascript(new_expr, code_bytes);
assert!(!intents.is_empty());
assert_eq!(intents[0].method, "MyClass");
assert!(intents[0].receiver.is_none());
}
}
#[test]
fn test_extract_class_inheritance_js() {
crate::pipeline::parser::test_utils::assert_js_class_inheritance(
"class Child extends Parent { }",
crate::pipeline::parser::test_utils::parse_javascript_snippet,
"Parent",
);
}
#[test]
fn test_extract_class_inheritance_js_qualified() {
crate::pipeline::parser::test_utils::assert_js_class_inheritance(
"class Child extends NS.Parent { }",
crate::pipeline::parser::test_utils::parse_javascript_snippet,
"Parent",
);
}
#[test]
fn test_extract_jsx_attributes_multiple() {
crate::pipeline::parser::test_utils::assert_jsx_attributes_multi(
r#"function Form() { return <input id="email-input" className="form-control" />; }"#,
crate::pipeline::parser::test_utils::parse_javascript_snippet,
2,
&[("id", "email-input"), ("className", "form-control")],
);
}
#[test]
fn test_extract_jsx_attributes_classname() {
crate::pipeline::parser::test_utils::assert_jsx_attribute(
r#"function Button() { return <button className="btn primary">Click</button>; }"#,
crate::pipeline::parser::test_utils::parse_javascript_snippet,
"className",
"btn primary",
);
}
#[test]
fn test_extract_require_module_path() {
let code = "var MyJsAlias = require('./alias_target_js');";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
let var_node = find_var_decl(tree.root_node()).unwrap();
let path = extract_require_module_path(var_node, code.as_bytes());
assert_eq!(path.as_deref(), Some("./alias_target_js"));
}
#[test]
fn test_js_import_named_emits_ref() {
let code = "import { Foo } from './types';";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
let mut intents = Vec::new();
collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
let has_foo = intents.iter().any(|(i, _)| match i {
ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
_ => false,
});
assert!(
has_foo,
"Should emit ValueReference for Foo from named import, got: {:?}",
intents
);
}
#[test]
fn test_js_import_aliased_uses_original() {
let code = "import { Foo as Bar } from './types';";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
let mut intents = Vec::new();
collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
let has_foo = intents.iter().any(|(i, _)| match i {
ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
_ => false,
});
let has_bar = intents.iter().any(|(i, _)| match i {
ReferenceIntent::ValueReference { value_name, .. } => value_name == "Bar",
_ => false,
});
assert!(
has_foo,
"Should emit for Foo (original), got: {:?}",
intents
);
assert!(
!has_bar,
"Should NOT emit for Bar (alias), got: {:?}",
intents
);
}
#[test]
fn test_js_require_destructure_emits_refs() {
let code = "const { Foo, helper } = require('./m');";
let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
.expect("Failed to parse JavaScript code");
let mut intents = Vec::new();
collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
let has_foo = intents.iter().any(|(i, _)| match i {
ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
_ => false,
});
assert!(
has_foo,
"Should emit ValueReference for Foo from require destructure, got: {:?}",
intents
);
}
}