mant-ir 0.11.0

Normalized, source-neutral document intermediate representation for ManT
Documentation
use super::*;
use crate::{
    DefinitionItem, DefinitionLayout, Inline, ListItem, ListItemLayout, ListKind, TableCell,
    TableRow, TextRange,
};

fn source() -> SourceSpan {
    SourceSpan {
        byte_range: Some(TextRange::new(
            crate::TextSize::new(7),
            crate::TextSize::new(21),
        )),
        line: 2,
        column: 3,
        end_line: Some(4),
        end_column: Some(5),
    }
}

fn layout() -> LayoutHint {
    LayoutHint {
        indent_columns: -3,
        continuation_indent_columns: 6,
        spacing_before_lines: 2,
    }
}

fn variants(source: Option<SourceSpan>) -> Vec<(Block, bool)> {
    let layout = layout();
    vec![
        (
            Block::Paragraph {
                children: Vec::new(),
                layout,
                source,
            },
            true,
        ),
        (
            Block::Preformatted {
                children: Vec::new(),
                language: Some("roff".into()),
                layout,
                source,
            },
            true,
        ),
        (
            Block::List {
                kind: ListKind::Bullet,
                compact: true,
                items: Vec::new(),
                layout,
                source,
            },
            true,
        ),
        (
            Block::DefinitionList {
                items: Vec::new(),
                declaration_groups: Vec::new(),
                compact: false,
                layout,
                source,
            },
            true,
        ),
        (
            Block::Table {
                rows: Vec::new(),
                layout,
                source,
            },
            true,
        ),
        (
            Block::Equation {
                value: "x + y".into(),
                display: true,
                layout,
                source,
            },
            true,
        ),
        (
            Block::Unsupported {
                name: Some("unknown".into()),
                text: "BODY".into(),
                layout,
                source,
            },
            true,
        ),
        (Block::VerticalSpace { lines: 3, source }, false),
        (Block::ThematicBreak { source }, false),
    ]
}

#[test]
fn every_block_layout_accessor_agrees_and_keeps_original_source() {
    for source in [None, Some(source())] {
        for (mut block, carries_layout) in variants(source) {
            assert_eq!(block_source(&block), source);
            assert_eq!(block_layout(&block), carries_layout.then_some(&layout()));
            let original = block.clone();
            if let Some(hint) = block_layout_mut(&mut block) {
                hint.indent_columns = 12;
            }
            assert_eq!(block_source(&block), source);
            if carries_layout {
                let hint = block_layout(&block).unwrap();
                assert_eq!(hint.indent_columns, 12);
                assert_eq!(hint.continuation_indent_columns, 6);
                assert_eq!(hint.spacing_before_lines, 2);
            } else {
                assert_eq!(block, original);
            }
        }
    }
}

#[test]
fn reparenting_all_variants_changes_only_root_origins() {
    for (old_parent, new_parent) in [(10, -5), (-5, 10), (7, 7)] {
        for (block, carries_layout) in variants(Some(source())) {
            let mut blocks = vec![block.clone()];
            rebase_roots(&mut blocks, old_parent, new_parent);
            let mut expected = block;
            if carries_layout {
                block_layout_mut(&mut expected).unwrap().indent_columns =
                    -3 + old_parent - new_parent;
                assert_eq!(
                    block_layout(&blocks[0]).unwrap().indent_columns + new_parent,
                    -3 + old_parent
                );
            }
            assert_eq!(blocks, [expected]);
        }
    }
}

#[test]
fn nested_reparenting_never_translates_descendants_or_source_twice() {
    let paragraph = Block::Paragraph {
        children: vec![
            Inline::anchor("target"),
            Inline::Text {
                value: "UNCHANGED".into(),
            },
        ],
        layout: layout(),
        source: Some(source()),
    };
    let table = Block::Table {
        rows: vec![TableRow {
            cells: vec![TableCell {
                blocks: vec![paragraph],
                column_span: 1,
                row_span: 1,
                alignment: None,
            }],
        }],
        layout: layout(),
        source: Some(source()),
    };
    let definition = Block::DefinitionList {
        items: vec![DefinitionItem {
            terms: vec![vec![Inline::Text {
                value: "TERM".into(),
            }]],
            description: vec![table],
            entry: None,
            source: Some(source()),
            layout: DefinitionLayout::default(),
        }],
        declaration_groups: Vec::new(),
        compact: false,
        layout: layout(),
        source: Some(source()),
    };
    let mut blocks = vec![Block::List {
        kind: ListKind::Ordered { start: Some(5) },
        compact: false,
        items: vec![ListItem {
            blocks: vec![definition],
            entry: None,
            source: Some(source()),
            layout: ListItemLayout::default(),
        }],
        layout: layout(),
        source: Some(source()),
    }];
    let original = blocks.clone();
    rebase_roots(&mut blocks, 10, 4);
    assert_eq!(block_layout(&blocks[0]).unwrap().indent_columns, 3);
    block_layout_mut(&mut blocks[0]).unwrap().indent_columns = -3;
    assert_eq!(blocks, original);
}

#[test]
fn extreme_parent_origins_saturate_only_the_moved_root() {
    for (old_parent, new_parent, expected) in [
        (i32::MAX, i32::MIN, i32::MAX),
        (i32::MIN, i32::MAX, i32::MIN),
        (i32::MAX, i32::MAX, -3),
        (i32::MIN, i32::MIN, -3),
    ] {
        let mut blocks = vec![variants(Some(source())).remove(0).0];
        rebase_roots(&mut blocks, old_parent, new_parent);
        assert_eq!(block_layout(&blocks[0]).unwrap().indent_columns, expected);
        assert_eq!(block_source(&blocks[0]), Some(source()));
    }
}