lisette-semantics 0.2.13

Little language inspired by Rust that compiles to Go
Documentation
use diagnostics::LisetteDiagnostic;
use syntax::ast::{Expression, FormatStringPart, Literal, Pattern};

pub fn check_invisible_in_string_expression(
    expression: &Expression,
    diagnostics: &mut Vec<LisetteDiagnostic>,
) {
    let Expression::Literal { literal, span, .. } = expression else {
        return;
    };
    let found = match literal {
        Literal::String { value, .. } => first_invisible(value),
        Literal::FormatString(parts) => parts.iter().find_map(|part| match part {
            FormatStringPart::Text(text) => first_invisible(text),
            FormatStringPart::Expression(_) => None,
        }),
        _ => None,
    };
    if let Some((codepoint, name, is_bidi)) = found {
        diagnostics.push(diagnostics::lint::invisible_in_string(
            span, codepoint, name, is_bidi,
        ));
    }
}

pub fn check_invisible_in_string_pattern(
    pattern: &Pattern,
    diagnostics: &mut Vec<LisetteDiagnostic>,
) {
    let Pattern::Literal {
        literal: Literal::String { value, .. },
        span,
        ..
    } = pattern
    else {
        return;
    };
    if let Some((codepoint, name, is_bidi)) = first_invisible(value) {
        diagnostics.push(diagnostics::lint::invisible_in_string(
            span, codepoint, name, is_bidi,
        ));
    }
}

fn first_invisible(text: &str) -> Option<(u32, &'static str, bool)> {
    text.chars()
        .find_map(|c| classify_invisible(c).map(|(name, is_bidi)| (c as u32, name, is_bidi)))
}

fn classify_invisible(c: char) -> Option<(&'static str, bool)> {
    match c {
        '\u{00A0}' => Some(("no-break space", false)),
        '\u{200B}' => Some(("zero-width space", false)),
        '\u{200C}' => Some(("zero-width non-joiner", false)),
        '\u{200D}' => Some(("zero-width joiner", false)),
        '\u{202A}' => Some(("left-to-right embedding", true)),
        '\u{202B}' => Some(("right-to-left embedding", true)),
        '\u{202C}' => Some(("pop directional formatting", true)),
        '\u{202D}' => Some(("left-to-right override", true)),
        '\u{202E}' => Some(("right-to-left override", true)),
        '\u{2066}' => Some(("left-to-right isolate", true)),
        '\u{2067}' => Some(("right-to-left isolate", true)),
        '\u{2068}' => Some(("first strong isolate", true)),
        '\u{2069}' => Some(("pop directional isolate", true)),
        '\u{FEFF}' => Some(("zero-width no-break space", false)),
        _ => None,
    }
}