vize_l2 0.567.0

L2 - the Davinci semantic IR (codename Disegno): the neutral op family every input dialect lowers into
Documentation
use super::{JsxMarkupIssue as Issue, JsxMarkupIssueKind as Kind};
use crate::lang::js::jsx_body::records::Record;
use crate::resolution::SyntaxKind as Syntax;
use alloc::vec::Vec;

pub(super) struct Children<'r, 'a> {
    records: &'r [Record<'a>],
    next: usize,
    end: usize,
}

pub(super) fn children<'r, 'a>(records: &'r [Record<'a>], index: usize) -> Children<'r, 'a> {
    Children {
        records,
        next: index + 1,
        end: records
            .get(index)
            .map_or(index + 1, |record| record.subtree_end),
    }
}

impl<'a> Iterator for Children<'_, 'a> {
    type Item = usize;
    fn next(&mut self) -> Option<usize> {
        if self.next >= self.end {
            return None;
        }
        let index = self.next;
        self.next = self.records.get(index)?.subtree_end;
        Some(index)
    }
}

pub(super) fn roots(records: &[Record<'_>], source: &str) -> (Vec<usize>, Vec<Issue>) {
    let mut roots = Vec::new();
    let mut issues = Vec::new();
    for (index, record) in records
        .iter()
        .enumerate()
        .filter(|(_, row)| row.parent.is_none())
    {
        let subtree = records.get(index..record.subtree_end).unwrap_or(&[]);
        if !subtree
            .iter()
            .any(|row| matches!(row.kind, Syntax::Element | Syntax::Fragment))
        {
            continue;
        }
        let mut body = children(records, index);
        let first = body.next();
        let root = first.and_then(|index| records.get(index).map(|row| (index, row)));
        let result = match root {
            Some((root, row))
                if record.kind == Syntax::Expression
                    && matches!(row.kind, Syntax::Element | Syntax::Fragment)
                    && record.span == row.span
                    && body.next().is_none() =>
            {
                node(records, root, source).map(|()| root)
            }
            _ => Err(Issue {
                kind: Kind::RootExpression,
                span: record.span,
            }),
        };
        match result {
            Ok(root) => roots.push(root),
            Err(issue) => issues.push(issue),
        }
    }
    (roots, issues)
}

fn node(records: &[Record<'_>], index: usize, source: &str) -> Result<(), Issue> {
    if record(records, index)?.kind == Syntax::Fragment {
        return fragment(records, index, source);
    }
    element(records, index, source)
}

fn fragment(records: &[Record<'_>], index: usize, source: &str) -> Result<(), Issue> {
    let row = record(records, index)?;
    let mut body = children(records, index);
    let opening = body.next().and_then(|index| records.get(index));
    if opening.is_none_or(|opening| {
        opening.kind != Syntax::FragmentOpening || opening.span.start != row.span.start
    }) {
        return Err(Issue {
            kind: Kind::InvalidRecord,
            span: row.span,
        });
    }
    while let Some(index) = body.next() {
        let row_child = record(records, index)?;
        if row_child.kind == Syntax::FragmentClosing {
            return if row_child.span.end == row.span.end && body.next().is_none() {
                Ok(())
            } else {
                Err(Issue {
                    kind: Kind::InvalidRecord,
                    span: row_child.span,
                })
            };
        }
        child(records, index, source)?;
    }
    Err(Issue {
        kind: Kind::InvalidRecord,
        span: row.span,
    })
}

fn element(records: &[Record<'_>], index: usize, source: &str) -> Result<(), Issue> {
    let row = record(records, index)?;
    let mut body = children(records, index);
    let opening = body.next().ok_or(Issue {
        kind: Kind::InvalidRecord,
        span: row.span,
    })?;
    let tag = header(records, opening, source)?;
    for child in body {
        let row = record(records, child)?;
        match row.kind {
            Syntax::Closing => {
                let mut names = children(records, child);
                if names
                    .next()
                    .and_then(|name| records.get(name))
                    .is_some_and(|row| row.kind == Syntax::Intrinsic(tag))
                    && names.next().is_none()
                {
                    continue;
                }
                return Err(Issue {
                    kind: Kind::InvalidRecord,
                    span: row.span,
                });
            }
            _ => self::child(records, child, source)?,
        }
    }
    Ok(())
}

fn child(records: &[Record<'_>], index: usize, source: &str) -> Result<(), Issue> {
    let row = record(records, index)?;
    let kind = match row.kind {
        Syntax::Element | Syntax::Fragment => return node(records, index, source),
        Syntax::Text(value) => {
            let original = source.get(row.span.start as usize..row.span.end as usize);
            if super::whitespace::ignored(value) && original == Some(value) {
                return Ok(());
            }
            return normalized(row, value, source);
        }
        Syntax::Container if empty(records, index) => return Ok(()),
        Syntax::Container => Kind::DynamicChild,
        Syntax::SpreadChild => Kind::SpreadChild,
        _ => Kind::InvalidRecord,
    };
    Err(Issue {
        kind,
        span: row.span,
    })
}

fn header<'a>(records: &[Record<'a>], index: usize, source: &str) -> Result<&'a str, Issue> {
    let row = record(records, index)?;
    let mut body = children(records, index);
    let name = body
        .next()
        .and_then(|index| records.get(index))
        .ok_or(Issue {
            kind: Kind::InvalidRecord,
            span: row.span,
        })?;
    let tag = match name.kind {
        Syntax::Intrinsic(tag) if !matches!(tag, "svg" | "math") => tag,
        Syntax::Intrinsic(_) => {
            return Err(Issue {
                kind: Kind::Namespace,
                span: name.span,
            });
        }
        Syntax::Component(_) | Syntax::Member => {
            return Err(Issue {
                kind: Kind::Component,
                span: name.span,
            });
        }
        _ => {
            return Err(Issue {
                kind: Kind::InvalidRecord,
                span: name.span,
            });
        }
    };
    for child in body {
        let row = record(records, child)?;
        match row.kind {
            Syntax::Attribute(name) => attribute(records, child, name, source)?,
            Syntax::SpreadAttribute => {
                return Err(Issue {
                    kind: Kind::SpreadAttribute,
                    span: row.span,
                });
            }
            _ => {
                return Err(Issue {
                    kind: Kind::InvalidRecord,
                    span: row.span,
                });
            }
        }
    }
    Ok(tag)
}

fn attribute(records: &[Record<'_>], index: usize, name: &str, source: &str) -> Result<(), Issue> {
    let row = record(records, index)?;
    if name.starts_with("v-")
        || name.starts_with("vModel")
        || name.starts_with("vSlots")
        || name.starts_with("on")
        || matches!(
            name,
            "key" | "ref" | "is" | "innerHTML" | "textContent" | "xmlns"
        )
    {
        return Err(Issue {
            kind: Kind::SpecialAttribute,
            span: row.span,
        });
    }
    let mut parts = children(records, index);
    if parts
        .next()
        .and_then(|index| records.get(index))
        .is_none_or(|row| row.kind != Syntax::AttributeName(name))
    {
        return Err(Issue {
            kind: Kind::InvalidRecord,
            span: row.span,
        });
    }
    if let Some(value) = parts.next().and_then(|index| records.get(index)) {
        let Syntax::AttributeString(text) = value.kind else {
            return Err(Issue {
                kind: Kind::DynamicAttribute,
                span: value.span,
            });
        };
        normalized(value, text, source)?;
    }
    if parts.next().is_some() {
        return Err(Issue {
            kind: Kind::InvalidRecord,
            span: row.span,
        });
    }
    Ok(())
}

fn normalized(row: &Record<'_>, value: &str, source: &str) -> Result<(), Issue> {
    let raw = source
        .get(row.span.start as usize..row.span.end as usize)
        .unwrap_or("");
    if raw.contains('&')
        || value.contains('&')
        || raw.bytes().any(|byte| byte.is_ascii_control())
        || value.bytes().any(|byte| byte.is_ascii_control())
    {
        return Err(Issue {
            kind: Kind::ValueNormalization,
            span: row.span,
        });
    }
    Ok(())
}

pub(super) fn empty(records: &[Record<'_>], index: usize) -> bool {
    let mut parts = children(records, index);
    parts
        .next()
        .and_then(|index| records.get(index))
        .is_some_and(|row| row.kind == Syntax::Empty)
        && parts.next().is_none()
}

fn record<'r, 'a>(records: &'r [Record<'a>], index: usize) -> Result<&'r Record<'a>, Issue> {
    records.get(index).ok_or(Issue {
        kind: Kind::InvalidRecord,
        span: vize_l0::Span::new(0, 0),
    })
}