use tree_sitter::Node;
use super::intelligence::node_text;
use super::types::*;
use super::walk::{Descend, walk_bounded, warn_if_truncated};
pub(super) fn structure(root: &Node<'_>, source: &str) -> Vec<StructureItem> {
let mut items = Vec::new();
let mut stage: Option<Stage> = None;
let truncated = walk_bounded(root, |node, _depth| {
match node.kind() {
"from_instruction" => {
close(&mut stage, &mut items, source);
stage = node.child_by_field_name("as").map(|alias| {
let start = node.start_position();
Stage {
name: node_text(&alias, source).to_string(),
start: node.start_byte(),
start_position: (start.row, start.column),
end: node.end_byte(),
children: Vec::new(),
}
});
}
"arg_instruction" => {
if let Some(name) = node.child_by_field_name("name") {
push(&mut stage, &mut items, constant(&name, node, source));
}
extend(&mut stage, node);
}
"env_instruction" => {
let mut cursor = node.walk();
for pair in node
.named_children(&mut cursor)
.filter(|child| child.kind() == "env_pair")
{
if let Some(name) = pair.child_by_field_name("name") {
push(&mut stage, &mut items, constant(&name, node, source));
}
}
extend(&mut stage, node);
}
"source_file" | "ERROR" => return Descend::Children,
"comment" => {}
kind if kind.ends_with("_instruction") => extend(&mut stage, node),
_ => {}
}
Descend::Skip
});
warn_if_truncated(truncated, "intel::dockerfile", "dockerfile");
close(&mut stage, &mut items, source);
items
}
struct Stage {
name: String,
start: usize,
start_position: (usize, usize),
end: usize,
children: Vec<StructureItem>,
}
fn constant(name: &Node<'_>, instruction: &Node<'_>, source: &str) -> StructureItem {
StructureItem {
kind: StructureKind::Other("Constant".to_string()),
name: Some(node_text(name, source).to_string()),
span: super::intelligence::span_trimmed(instruction, source),
..StructureItem::default()
}
}
fn push(stage: &mut Option<Stage>, items: &mut Vec<StructureItem>, item: StructureItem) {
match stage {
Some(stage) => stage.children.push(item),
None => items.push(item),
}
}
fn extend(stage: &mut Option<Stage>, instruction: &Node<'_>) {
if let Some(stage) = stage {
stage.end = stage.end.max(instruction.end_byte());
}
}
fn close(stage: &mut Option<Stage>, items: &mut Vec<StructureItem>, source: &str) {
if let Some(stage) = stage.take() {
items.push(StructureItem {
kind: StructureKind::Module,
name: Some(stage.name),
span: super::intelligence::span_between_from(source, stage.start, stage.end, stage.start_position),
children: stage.children,
..StructureItem::default()
});
}
}