use alloc::vec::Vec;
use crate::stack::build::StackBuilder;
use crate::stack::{Definition, StackNode, SymbolPop};
use crate::syntax::{SyntaxGraph, SyntaxKind, SyntaxNode};
use crate::{CodeGraph, NodeId};
impl StackBuilder {
pub(super) fn add_parameters(&mut self, syntax: &SyntaxGraph) {
let functions = syntax
.nodes_by_type
.get(&SyntaxKind::MethodDeclaration)
.map_or(&[][..], |ids| ids.as_slice());
for function in functions {
let Some(SyntaxNode::MethodDeclaration {
parameters_id: Some(parameters),
..
}) = syntax.nodes.get(function)
else {
continue;
};
let mut bindings = Vec::new();
collect_bindings(syntax, *parameters, &mut bindings);
bindings.retain(|(id, _)| !self.graph.nodes.contains_key(id));
for (id, name) in bindings {
self.graph.add_node(
id,
StackNode::PopSymbol(SymbolPop {
symbol: name.into(),
precedence: 1,
definition: Some(Definition::default()),
import: None,
}),
);
self.graph.add_edge(*function, id, 1);
}
}
}
}
fn collect_bindings<'s>(syntax: &'s SyntaxGraph, node: NodeId, out: &mut Vec<(NodeId, &'s str)>) {
let children = match syntax.nodes.get(&node) {
Some(SyntaxNode::SymbolLookup { symbol, .. }) => {
out.push((node, symbol.as_str()));
return;
}
Some(SyntaxNode::Parameter {
variable: Some(variable),
..
}) => {
let name = variable.trim_start_matches(['*', '.']);
if !name.is_empty() {
out.push((node, name));
}
return;
}
Some(SyntaxNode::Pair { value_id, .. } | SyntaxNode::RestPattern { value_id }) => {
Vec::from([*value_id])
}
Some(SyntaxNode::MissingNode { node_type }) if node_type == "assignment_pattern" => {
syntax.ast_children(node).into_iter().take(1).collect()
}
Some(
SyntaxNode::ParameterList
| SyntaxNode::Object { .. }
| SyntaxNode::ArrayInitializer { .. }
| SyntaxNode::MissingNode { .. },
) => syntax.ast_children(node),
_ => Vec::new(),
};
for child in children {
collect_bindings(syntax, child, out);
}
}
#[cfg(test)]
mod tests {
use crate::stack::{build_stack_graph, StackGraph, StackNode};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{Language, NodeId};
use alloc::string::String;
use alloc::vec::Vec;
const FILE: NodeId = NodeId(1);
const FUNCTION: NodeId = NodeId(2);
const PARAMETERS: NodeId = NodeId(3);
fn lookup(symbol: &str) -> SyntaxNode {
SyntaxNode::SymbolLookup {
symbol: String::from(symbol),
symbol_scope: Some(FUNCTION),
value: None,
}
}
fn parameter(variable: &str) -> SyntaxNode {
SyntaxNode::Parameter {
variable: Some(String::from(variable)),
variable_type: None,
value_id: None,
parameter_mode: None,
}
}
fn missing(node_type: &str) -> SyntaxNode {
SyntaxNode::MissingNode {
node_type: String::from(node_type),
}
}
fn rest(value: u64) -> SyntaxNode {
SyntaxNode::RestPattern {
value_id: NodeId(value),
}
}
fn pair(key: u64, value: u64) -> SyntaxNode {
SyntaxNode::Pair {
key_id: NodeId(key),
value_id: NodeId(value),
}
}
fn function_with(nodes: &[(NodeId, NodeId, SyntaxNode)]) -> SyntaxGraph {
let mut syntax = SyntaxGraph::new();
syntax.add_node(FILE, SyntaxNode::File);
syntax.add_node(
FUNCTION,
SyntaxNode::MethodDeclaration {
name: None,
access_modifiers: None,
block_id: None,
modifiers_id: None,
parameters_id: Some(PARAMETERS),
},
);
syntax.add_ast_edge(FILE, FUNCTION);
syntax.scope_parent.insert(FUNCTION, FILE);
for (parent, id, node) in nodes {
syntax.add_node(*id, node.clone());
syntax.add_ast_edge(*parent, *id);
}
syntax
}
fn defined(graph: &StackGraph) -> Vec<(NodeId, &str)> {
graph
.successors(FUNCTION)
.into_iter()
.filter_map(|(id, _)| {
let node = graph.nodes.get(&id).filter(|node| node.is_definition())?;
Some((id, node.symbol()?))
})
.collect()
}
#[test]
fn plain_typed_and_rest_parameters_are_defined_in_their_function() {
let syntax = function_with(&[
(FUNCTION, PARAMETERS, SyntaxNode::ParameterList),
(PARAMETERS, NodeId(4), lookup("a")),
(PARAMETERS, NodeId(5), parameter("b")),
(PARAMETERS, NodeId(6), rest(7)),
(NodeId(6), NodeId(7), lookup("args")),
(PARAMETERS, NodeId(8), parameter("...rest")),
]);
let graph = build_stack_graph(&syntax, Language::Python, "mod.py");
assert_eq!(
defined(&graph),
[
(NodeId(4), "a"),
(NodeId(5), "b"),
(NodeId(7), "args"),
(NodeId(8), "rest")
]
);
}
#[test]
fn a_default_value_is_not_a_parameter_and_stays_a_reference() {
let syntax = function_with(&[
(FUNCTION, PARAMETERS, SyntaxNode::ParameterList),
(PARAMETERS, NodeId(4), missing("assignment_pattern")),
(NodeId(4), NodeId(5), lookup("b")),
(NodeId(4), NodeId(6), lookup("fallback")),
]);
let graph = build_stack_graph(&syntax, Language::JavaScript, "mod.js");
assert_eq!(defined(&graph), [(NodeId(5), "b")]);
let fallback = graph.nodes.get(&NodeId(6));
assert!(fallback.is_some_and(StackNode::is_reference));
}
#[test]
fn destructured_parameters_define_their_bound_names_and_not_their_keys() {
let syntax = function_with(&[
(FUNCTION, PARAMETERS, SyntaxNode::ParameterList),
(
PARAMETERS,
NodeId(4),
SyntaxNode::Object {
name: None,
tf_reference: None,
},
),
(NodeId(4), NodeId(5), lookup("k")),
(NodeId(4), NodeId(6), pair(7, 8)),
(NodeId(6), NodeId(7), lookup("m")),
(NodeId(6), NodeId(8), lookup("n")),
(PARAMETERS, NodeId(9), missing("array_pattern")),
(NodeId(9), NodeId(10), lookup("u")),
]);
let graph = build_stack_graph(&syntax, Language::JavaScript, "mod.js");
assert_eq!(
defined(&graph),
[(NodeId(5), "k"), (NodeId(8), "n"), (NodeId(10), "u")]
);
}
#[test]
fn a_lone_arrow_parameter_is_defined_in_its_function() {
let syntax = function_with(&[(FUNCTION, PARAMETERS, parameter("q"))]);
assert_eq!(
defined(&build_stack_graph(&syntax, Language::JavaScript, "mod.js")),
[(PARAMETERS, "q")]
);
}
}