use alloc::string::String;
use alloc::vec::Vec;
use crate::syntax::builders::utils::{bound_import_symbol, register_symbol_in_scope};
use crate::syntax::SyntaxNode;
use crate::syntax::{SyntaxGraphArgs, SyntaxGraphError};
use crate::NodeId;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ImportLabel {
#[default]
Import,
ModuleImport,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct ImportElement {
pub expression: Option<String>,
pub alias: Option<String>,
pub method_name: Option<String>,
pub corrected_n_id: Option<NodeId>,
pub label_type: ImportLabel,
pub module_nodes: Vec<NodeId>,
}
fn element_node(element: &ImportElement, expression: String, alias: Option<String>) -> SyntaxNode {
match element.label_type {
ImportLabel::Import => SyntaxNode::Import {
expression: Some(expression),
alias,
method_name: element.method_name.clone(),
import_type: None,
},
ImportLabel::ModuleImport => SyntaxNode::ModuleImport { expression, alias },
}
}
pub fn build_import_node(
args: &mut SyntaxGraphArgs<'_>,
n_id: NodeId,
element: &ImportElement,
) -> Result<NodeId, SyntaxGraphError> {
let expression = element.expression.clone().unwrap_or_default();
let alias = element.alias.clone().filter(|value| !value.is_empty());
let bound = bound_import_symbol(&expression, alias.as_deref());
args.syntax_graph
.add_node(n_id, element_node(element, expression, alias));
for node in &element.module_nodes {
let built = args.generic(*node)?;
args.syntax_graph.add_ast_edge(n_id, built);
}
if !bound.is_empty() {
register_symbol_in_scope(args.syntax_graph, args.metadata, bound, n_id);
}
Ok(n_id)
}
pub fn build_wildcard_import_node(
args: &mut SyntaxGraphArgs<'_>,
n_id: NodeId,
) -> Result<NodeId, SyntaxGraphError> {
build_import_node(
args,
n_id,
&ImportElement {
expression: Some(String::from("*")),
..ImportElement::default()
},
)
}
fn build_multiple_import_node(
args: &mut SyntaxGraphArgs<'_>,
n_id: NodeId,
imported_elements: &[ImportElement],
wildcards: &[NodeId],
) -> Result<NodeId, SyntaxGraphError> {
args.syntax_graph.add_node(
n_id,
SyntaxNode::Import {
expression: None,
alias: None,
method_name: None,
import_type: Some(String::from("multiple_import")),
},
);
for element in imported_elements {
if let Some(corrected_n_id) = element.corrected_n_id {
let built = build_import_node(args, corrected_n_id, element)?;
args.syntax_graph.add_ast_edge(n_id, built);
}
}
for child_n_id in wildcards {
let built = build_wildcard_import_node(args, *child_n_id)?;
args.syntax_graph.add_ast_edge(n_id, built);
}
Ok(n_id)
}
#[allow(
dead_code,
reason = "wired when a consuming language reader lands, #26623"
)]
pub fn build_import_statement_node(
args: &mut SyntaxGraphArgs<'_>,
n_id: NodeId,
imported_elements: &[ImportElement],
always_wrap: bool,
wildcards: &[NodeId],
) -> Result<NodeId, SyntaxGraphError> {
match imported_elements {
[element] if !always_wrap && wildcards.is_empty() => build_import_node(args, n_id, element),
_ => build_multiple_import_node(args, n_id, imported_elements, wildcards),
}
}
#[cfg(test)]
mod tests {
use super::{build_import_statement_node, ImportElement, ImportLabel};
use crate::ast::AstGraph;
use crate::syntax::{SyntaxGraph, SyntaxGraphArgs, SyntaxMetadata, SyntaxNode, SyntaxReader};
use crate::{Language, NodeId};
use alloc::string::String;
use alloc::vec;
fn no_dispatch(_: &str) -> Option<SyntaxReader> {
None
}
#[test]
fn a_single_element_is_added_directly_without_the_wrapper_node() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let elements = vec![ImportElement {
expression: Some(String::from("os")),
corrected_n_id: Some(NodeId(99)),
..ImportElement::default()
}];
let result = {
let mut args =
SyntaxGraphArgs::new(Language::Python, &ast, &mut graph, &mut meta, no_dispatch);
build_import_statement_node(&mut args, NodeId(1), &elements, false, &[])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
let Some(SyntaxNode::Import {
expression,
import_type,
..
}) = graph.nodes.get(&NodeId(1))
else {
panic!("the builder must emit the expected node");
};
assert_eq!(expression.as_deref(), Some("os"));
assert_eq!(*import_type, None);
assert!(!graph.nodes.contains_key(&NodeId(99)));
}
#[test]
fn multiple_elements_wrap_and_wire_only_the_corrected_ones() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let elements = vec![
ImportElement {
expression: Some(String::from("a")),
corrected_n_id: Some(NodeId(2)),
..ImportElement::default()
},
ImportElement {
expression: Some(String::from("b")),
alias: Some(String::from("bee")),
..ImportElement::default()
},
];
let result = {
let mut args =
SyntaxGraphArgs::new(Language::Python, &ast, &mut graph, &mut meta, no_dispatch);
build_import_statement_node(&mut args, NodeId(1), &elements, false, &[])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
let Some(SyntaxNode::Import { import_type, .. }) = graph.nodes.get(&NodeId(1)) else {
panic!("the builder must emit the expected node");
};
assert_eq!(import_type.as_deref(), Some("multiple_import"));
let Some(SyntaxNode::Import {
expression: child_expression,
import_type: child_import_type,
..
}) = graph.nodes.get(&NodeId(2))
else {
panic!("the corrected element must be wired as an Import node");
};
assert_eq!(child_expression.as_deref(), Some("a"));
assert_eq!(*child_import_type, None);
assert!(graph
.edges
.get(&NodeId(1))
.is_some_and(|adjacent| adjacent.contains_key(&NodeId(2))));
assert_eq!(graph.nodes.len(), 2);
}
#[test]
fn an_empty_element_list_still_produces_the_wrapper_node() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let result = {
let mut args =
SyntaxGraphArgs::new(Language::Python, &ast, &mut graph, &mut meta, no_dispatch);
build_import_statement_node(&mut args, NodeId(1), &[], false, &[])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
assert_eq!(graph.nodes.len(), 1);
assert!(!graph.edges.contains_key(&NodeId(1)));
let Some(SyntaxNode::Import { import_type, .. }) = graph.nodes.get(&NodeId(1)) else {
panic!("the builder must emit the wrapper Import node");
};
assert_eq!(import_type.as_deref(), Some("multiple_import"));
}
#[test]
fn items_and_wildcards_become_ast_children_of_the_import() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let elements = vec![ImportElement {
expression: Some(String::from("os")),
alias: Some(String::from("operating_system")),
corrected_n_id: Some(NodeId(2)),
..ImportElement::default()
}];
let result = {
let mut args =
SyntaxGraphArgs::new(Language::Python, &ast, &mut graph, &mut meta, no_dispatch);
build_import_statement_node(&mut args, NodeId(1), &elements, false, &[NodeId(3)])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
let Some(SyntaxNode::Import { import_type, .. }) = graph.nodes.get(&NodeId(1)) else {
panic!("the builder must emit the expected node");
};
assert_eq!(import_type.as_deref(), Some("multiple_import"));
assert!(graph.edges.get(&NodeId(1)).is_some_and(|adjacent| adjacent
.contains_key(&NodeId(2))
&& adjacent.contains_key(&NodeId(3))));
let Some(SyntaxNode::Import {
expression, alias, ..
}) = graph.nodes.get(&NodeId(2))
else {
panic!("the item must be wired as an Import node");
};
assert_eq!(expression.as_deref(), Some("os"));
assert_eq!(alias.as_deref(), Some("operating_system"));
let Some(SyntaxNode::Import {
expression: wildcard_expression,
..
}) = graph.nodes.get(&NodeId(3))
else {
panic!("the wildcard must be wired as an Import node");
};
assert_eq!(wildcard_expression.as_deref(), Some("*"));
}
#[test]
fn always_wrap_keeps_the_wrapper_node_for_a_lone_element() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let elements = vec![ImportElement {
expression: Some(String::from("os")),
corrected_n_id: Some(NodeId(2)),
..ImportElement::default()
}];
let result = {
let mut args =
SyntaxGraphArgs::new(Language::Python, &ast, &mut graph, &mut meta, no_dispatch);
build_import_statement_node(&mut args, NodeId(1), &elements, true, &[])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
let Some(SyntaxNode::Import { import_type, .. }) = graph.nodes.get(&NodeId(1)) else {
panic!("the builder must emit the wrapper Import node");
};
assert_eq!(import_type.as_deref(), Some("multiple_import"));
let Some(SyntaxNode::Import { expression, .. }) = graph.nodes.get(&NodeId(2)) else {
panic!("the element must be wired as its own Import node");
};
assert_eq!(expression.as_deref(), Some("os"));
}
#[test]
fn a_module_import_element_emits_a_module_import_node() {
let ast = AstGraph::new();
let mut graph = SyntaxGraph::new();
let mut meta = SyntaxMetadata::seeded(NodeId(10));
let elements = vec![ImportElement {
expression: Some(String::from("./module.thing")),
alias: Some(String::from("thing")),
label_type: ImportLabel::ModuleImport,
..ImportElement::default()
}];
let result = {
let mut args = SyntaxGraphArgs::new(
Language::JavaScript,
&ast,
&mut graph,
&mut meta,
no_dispatch,
);
build_import_statement_node(&mut args, NodeId(1), &elements, false, &[])
};
let built_id = result.expect("the builder must succeed");
assert_eq!(built_id, NodeId(1));
let Some(SyntaxNode::ModuleImport { expression, alias }) = graph.nodes.get(&NodeId(1))
else {
panic!("the builder must emit a ModuleImport node");
};
assert_eq!(expression, "./module.thing");
assert_eq!(alias.as_deref(), Some("thing"));
}
}