aboutsummaryrefslogblamecommitdiffstats
path: root/tests/test_utils/hast.rs
blob: 857df4dfdb55d49c010423be2544cc8252f369cf (plain) (tree)
1
2
3
4
5
6
7
8


                                               
 

                          

                   




                               

                                                                                   

          
                               





















































                                                                      
                                          









































































                                                                
                                      













                                   
                                      








                                                 
                                      
                        

                   

                                


















































                                      
                                      
                          






                                          




                                   














                                      


                                                            














                                      


                                                            
 



































































































































































































































                                                                                                        
//! HTML syntax tree: [hast][].
//!
//! [hast]: https://github.com/syntax-tree/hast

#![allow(dead_code)]
// ^-- To do: externalize.

extern crate alloc;
use alloc::{
    fmt,
    string::{String, ToString},
    vec::Vec,
};
pub use markdown::mdast::{AttributeContent, AttributeValue, MdxJsxAttribute, Stop};
use markdown::unist::Position;

/// Nodes.
#[derive(Clone, PartialEq, Eq)]
pub enum Node {
    /// Root.
    Root(Root),
    /// Element.
    Element(Element),
    /// Document type.
    Doctype(Doctype),
    /// Comment.
    Comment(Comment),
    /// Text.
    Text(Text),

    // MDX being passed through.
    /// MDX: JSX element.
    MdxJsxElement(MdxJsxElement),
    /// MDX.js ESM.
    MdxjsEsm(MdxjsEsm),
    // MDX: expression.
    MdxExpression(MdxExpression),
}

impl fmt::Debug for Node {
    // Debug the wrapped struct.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Node::Root(x) => write!(f, "{:?}", x),
            Node::Element(x) => write!(f, "{:?}", x),
            Node::Doctype(x) => write!(f, "{:?}", x),
            Node::Comment(x) => write!(f, "{:?}", x),
            Node::Text(x) => write!(f, "{:?}", x),
            Node::MdxJsxElement(x) => write!(f, "{:?}", x),
            Node::MdxExpression(x) => write!(f, "{:?}", x),
            Node::MdxjsEsm(x) => write!(f, "{:?}", x),
        }
    }
}

fn children_to_string(children: &[Node]) -> String {
    children.iter().map(ToString::to_string).collect()
}

impl ToString for Node {
    fn to_string(&self) -> String {
        match self {
            // Parents.
            Node::Root(x) => children_to_string(&x.children),
            Node::Element(x) => children_to_string(&x.children),
            Node::MdxJsxElement(x) => children_to_string(&x.children),
            // Literals.
            Node::Comment(x) => x.value.clone(),
            Node::Text(x) => x.value.clone(),
            Node::MdxExpression(x) => x.value.clone(),
            Node::MdxjsEsm(x) => x.value.clone(),
            // Voids.
            Node::Doctype(_) => "".into(),
        }
    }
}

impl Node {
    #[must_use]
    pub fn children(&self) -> Option<&Vec<Node>> {
        match self {
            // Parent.
            Node::Root(x) => Some(&x.children),
            Node::Element(x) => Some(&x.children),
            Node::MdxJsxElement(x) => Some(&x.children),
            // Non-parent.
            _ => None,
        }
    }

    pub fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
        match self {
            // Parent.
            Node::Root(x) => Some(&mut x.children),
            Node::Element(x) => Some(&mut x.children),
            Node::MdxJsxElement(x) => Some(&mut x.children),
            // Non-parent.
            _ => None,
        }
    }

    pub fn position(&self) -> Option<&Position> {
        match self {
            Node::Root(x) => x.position.as_ref(),
            Node::Element(x) => x.position.as_ref(),
            Node::Doctype(x) => x.position.as_ref(),
            Node::Comment(x) => x.position.as_ref(),
            Node::Text(x) => x.position.as_ref(),
            Node::MdxJsxElement(x) => x.position.as_ref(),
            Node::MdxExpression(x) => x.position.as_ref(),
            Node::MdxjsEsm(x) => x.position.as_ref(),
        }
    }

    pub fn position_mut(&mut self) -> Option<&mut Position> {
        match self {
            Node::Root(x) => x.position.as_mut(),
            Node::Element(x) => x.position.as_mut(),
            Node::Doctype(x) => x.position.as_mut(),
            Node::Comment(x) => x.position.as_mut(),
            Node::Text(x) => x.position.as_mut(),
            Node::MdxJsxElement(x) => x.position.as_mut(),
            Node::MdxExpression(x) => x.position.as_mut(),
            Node::MdxjsEsm(x) => x.position.as_mut(),
        }
    }

    pub fn position_set(&mut self, position: Option<Position>) {
        match self {
            Node::Root(x) => x.position = position,
            Node::Element(x) => x.position = position,
            Node::Doctype(x) => x.position = position,
            Node::Comment(x) => x.position = position,
            Node::Text(x) => x.position = position,
            Node::MdxJsxElement(x) => x.position = position,
            Node::MdxExpression(x) => x.position = position,
            Node::MdxjsEsm(x) => x.position = position,
        }
    }
}

/// Document.
///
/// ```html
/// > | a
///     ^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Root {
    // Parent.
    /// Content model.
    pub children: Vec<Node>,
    /// Positional info.
    pub position: Option<Position>,
}

/// Document type.
///
/// ```html
/// > | <!doctype html>
///     ^^^^^^^^^^^^^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Element {
    pub tag_name: String,
    pub properties: Vec<(String, PropertyValue)>,
    // Parent.
    pub children: Vec<Node>,
    /// Positional info.
    pub position: Option<Position>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PropertyValue {
    Boolean(bool),
    String(String),
    CommaSeparated(Vec<String>),
    SpaceSeparated(Vec<String>),
}

/// Document type.
///
/// ```html
/// > | <!doctype html>
///     ^^^^^^^^^^^^^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Doctype {
    // Void.
    /// Positional info.
    pub position: Option<Position>,
}

/// Comment.
///
/// ```html
/// > | <!-- a -->
///     ^^^^^^^^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Comment {
    // Text.
    /// Content model.
    pub value: String,
    /// Positional info.
    pub position: Option<Position>,
}

/// Text.
///
/// ```html
/// > | a
///     ^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Text {
    // Text.
    /// Content model.
    pub value: String,
    /// Positional info.
    pub position: Option<Position>,
}

/// MDX: JSX element.
///
/// ```markdown
/// > | <a />
///     ^^^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MdxJsxElement {
    // JSX element.
    /// Name.
    ///
    /// Fragments have no name.
    pub name: Option<String>,
    /// Attributes.
    pub attributes: Vec<AttributeContent>,
    // Parent.
    /// Content model.
    pub children: Vec<Node>,
    /// Positional info.
    pub position: Option<Position>,
}

/// MDX: expression.
///
/// ```markdown
/// > | {a}
///     ^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MdxExpression {
    // Literal.
    /// Content model.
    pub value: String,
    /// Positional info.
    pub position: Option<Position>,

    // Custom data on where each slice of `value` came from.
    pub stops: Vec<Stop>,
}

/// MDX: ESM.
///
/// ```markdown
/// > | import a from 'b'
///     ^^^^^^^^^^^^^^^^^
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MdxjsEsm {
    // Literal.
    /// Content model.
    pub value: String,
    /// Positional info.
    pub position: Option<Position>,

    // Custom data on where each slice of `value` came from.
    pub stops: Vec<Stop>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::{format, string::ToString, vec};
    use markdown::unist::Position;

    // Literals.

    #[test]
    fn text() {
        let mut node = Node::Text(Text {
            value: "a".into(),
            position: None,
        });

        assert_eq!(
            format!("{:?}", node),
            "Text { value: \"a\", position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "a", "should support `ToString`");
        assert_eq!(node.children_mut(), None, "should support `children_mut`");
        assert_eq!(node.children(), None, "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "Text { value: \"a\", position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }

    #[test]
    fn comment() {
        let mut node = Node::Comment(Comment {
            value: "a".into(),
            position: None,
        });

        assert_eq!(
            format!("{:?}", node),
            "Comment { value: \"a\", position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "a", "should support `ToString`");
        assert_eq!(node.children_mut(), None, "should support `children_mut`");
        assert_eq!(node.children(), None, "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "Comment { value: \"a\", position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }

    #[test]
    fn mdx_expression() {
        let mut node = Node::MdxExpression(MdxExpression {
            value: "a".into(),
            stops: vec![],
            position: None,
        });

        assert_eq!(
            format!("{:?}", node),
            "MdxExpression { value: \"a\", position: None, stops: [] }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "a", "should support `ToString`");
        assert_eq!(node.children_mut(), None, "should support `children_mut`");
        assert_eq!(node.children(), None, "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "MdxExpression { value: \"a\", position: Some(1:1-1:2 (0-1)), stops: [] }",
            "should support `position_set`"
        );
    }

    #[test]
    fn mdxjs_esm() {
        let mut node = Node::MdxjsEsm(MdxjsEsm {
            value: "a".into(),
            stops: vec![],
            position: None,
        });

        assert_eq!(
            format!("{:?}", node),
            "MdxjsEsm { value: \"a\", position: None, stops: [] }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "a", "should support `ToString`");
        assert_eq!(node.children_mut(), None, "should support `children_mut`");
        assert_eq!(node.children(), None, "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "MdxjsEsm { value: \"a\", position: Some(1:1-1:2 (0-1)), stops: [] }",
            "should support `position_set`"
        );
    }

    // Voids.

    #[test]
    fn doctype() {
        let mut node = Node::Doctype(Doctype { position: None });

        assert_eq!(
            format!("{:?}", node),
            "Doctype { position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "", "should support `ToString`");
        assert_eq!(node.children_mut(), None, "should support `children_mut`");
        assert_eq!(node.children(), None, "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "Doctype { position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }

    // Parents.

    #[test]
    fn root() {
        let mut node = Node::Root(Root {
            position: None,
            children: vec![],
        });

        assert_eq!(
            format!("{:?}", node),
            "Root { children: [], position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "", "should support `ToString`");
        assert_eq!(
            node.children_mut(),
            Some(&mut vec![]),
            "should support `children_mut`"
        );
        assert_eq!(node.children(), Some(&vec![]), "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "Root { children: [], position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }

    #[test]
    fn element() {
        let mut node = Node::Element(Element {
            tag_name: "a".into(),
            properties: vec![],
            position: None,
            children: vec![],
        });

        assert_eq!(
            format!("{:?}", node),
            "Element { tag_name: \"a\", properties: [], children: [], position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "", "should support `ToString`");
        assert_eq!(
            node.children_mut(),
            Some(&mut vec![]),
            "should support `children_mut`"
        );
        assert_eq!(node.children(), Some(&vec![]), "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "Element { tag_name: \"a\", properties: [], children: [], position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }

    #[test]
    fn mdx_jsx_element() {
        let mut node = Node::MdxJsxElement(MdxJsxElement {
            name: None,
            attributes: vec![],
            position: None,
            children: vec![],
        });

        assert_eq!(
            format!("{:?}", node),
            "MdxJsxElement { name: None, attributes: [], children: [], position: None }",
            "should support `Debug`"
        );
        assert_eq!(node.to_string(), "", "should support `ToString`");
        assert_eq!(
            node.children_mut(),
            Some(&mut vec![]),
            "should support `children_mut`"
        );
        assert_eq!(node.children(), Some(&vec![]), "should support `children`");
        assert_eq!(node.position(), None, "should support `position`");
        assert_eq!(node.position_mut(), None, "should support `position`");
        node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1)));
        assert_eq!(
            format!("{:?}", node),
            "MdxJsxElement { name: None, attributes: [], children: [], position: Some(1:1-1:2 (0-1)) }",
            "should support `position_set`"
        );
    }
}