yerba 0.10.0

YAML Editing and Refactoring with Better Accuracy
use super::*;

#[derive(Debug)]
pub struct NodeRecord {
  pub parent: Option<usize>,
  pub node_type: NodeType,
  pub selector: String,
  pub key_name: Option<String>,
  pub scalar: Option<ScalarValue>,
  pub has_value: bool,
  pub location: Location,
  pub key_location: Location,
}

fn location_of(lines: &LineIndex, range: TextRange) -> Location {
  compute_location(lines, range.start().into(), range.end().into())
}

fn collection_type(node: &SyntaxNode) -> Option<NodeType> {
  let mut flow = None;

  for descendant in node.descendants() {
    match descendant.kind() {
      SyntaxKind::BLOCK_SEQ => return Some(NodeType::Sequence),
      SyntaxKind::BLOCK_MAP => return Some(NodeType::Map),
      SyntaxKind::FLOW_SEQ if flow.is_none() => flow = Some(NodeType::Sequence),
      SyntaxKind::FLOW_MAP if flow.is_none() => flow = Some(NodeType::Map),
      _ => {}
    }
  }

  flow
}

struct Walker<'a> {
  lines: &'a LineIndex,
  records: Vec<NodeRecord>,
}

impl Walker<'_> {
  fn push(&mut self, parent: Option<usize>, node: Option<&SyntaxNode>, selector: String, key: Option<(String, Location)>) {
    let collection = node.and_then(collection_type);
    let scalar = match (node, collection) {
      (Some(node), None) => extract_scalar(node),
      _ => None,
    };
    let node_type = match (collection, &scalar) {
      (Some(node_type), _) => node_type,
      (None, Some(_)) => NodeType::Scalar,
      (None, None) => NodeType::NotFound,
    };
    let location = match node {
      Some(node) if parent.is_some() || !matches!(node_type, NodeType::Sequence) => location_of(self.lines, node.text_range()),
      _ => Location::default(),
    };
    let (key_name, key_location) = key.map(|(name, location)| (Some(name), location)).unwrap_or((None, Location::default()));

    let index = self.records.len();

    self.records.push(NodeRecord {
      parent,
      node_type,
      selector: selector.clone(),
      key_name,
      scalar,
      has_value: true,
      location,
      key_location,
    });

    if let Some(node) = node {
      match node_type {
        NodeType::Sequence => self.sequence_children(index, node, &selector),
        NodeType::Map => self.map_children(index, node, &selector),
        _ => {}
      }
    }
  }

  fn sequence_children(&mut self, index: usize, node: &SyntaxNode, selector: &str) {
    let entries: Vec<SyntaxNode> = if let Some(sequence) = find_block_sequence(node) {
      sequence.entries().map(|entry| entry.syntax().clone()).collect()
    } else if let Some(sequence) = find_flow_sequence(node) {
      flow_sequence_entries(&sequence)
    } else {
      Vec::new()
    };

    for (position, entry) in entries.iter().enumerate() {
      self.push(Some(index), Some(entry), format!("{}[{}]", selector, position), None);
    }
  }

  fn map_children(&mut self, index: usize, node: &SyntaxNode, selector: &str) {
    let entries: Vec<(SyntaxNode, Option<SyntaxNode>, Option<SyntaxNode>)> = if let Some(map) = find_block_map(node) {
      map
        .entries()
        .map(|entry| {
          (
            entry.syntax().clone(),
            entry.key().map(|key| key.syntax().clone()),
            entry.value().map(|value| value.syntax().clone()),
          )
        })
        .collect()
    } else if let Some(map) = find_flow_map(node) {
      flow_map_entries(&map)
        .into_iter()
        .map(|entry| {
          (
            entry.syntax().clone(),
            entry.key().map(|key| key.syntax().clone()),
            entry.value().map(|value| value.syntax().clone()),
          )
        })
        .collect()
    } else {
      Vec::new()
    };

    for (entry_node, key_node, value_node) in entries {
      let Some(key_node) = key_node else { continue };
      let Some(key_name) = extract_scalar_text(&key_node) else { continue };

      let key_location = location_of(self.lines, key_node.text_range());
      let child_selector = if selector.is_empty() {
        key_name.clone()
      } else {
        format!("{}.{}", selector, key_name)
      };

      match value_node {
        Some(value_node) => self.push(Some(index), Some(&value_node), child_selector, Some((key_name, key_location))),
        None => self.records.push(NodeRecord {
          parent: Some(index),
          node_type: NodeType::Scalar,
          selector: child_selector,
          key_name: Some(key_name),
          scalar: None,
          has_value: false,
          location: location_of(self.lines, entry_node.text_range()),
          key_location,
        }),
      }
    }
  }
}

impl Document {
  pub fn node_records(&self) -> Vec<NodeRecord> {
    let mut walker = Walker {
      lines: self.line_index(),
      records: Vec::new(),
    };

    if let Some(document) = Root::cast(self.root.clone()).and_then(|root| root.documents().next()) {
      walker.push(None, Some(document.syntax()), String::new(), None);
    }

    walker.records
  }
}