omena-bridge 0.2.0

CME-coupled bridge crate for Omena semantic graph inputs
Documentation
use oxc_span::SourceType;
use std::borrow::Cow;

pub(crate) fn is_vue_source(source_path: &str, source_language: Option<&str>) -> bool {
    source_language == Some("vue") || source_path.ends_with(".vue")
}

pub(crate) fn project_source_for_language<'a>(
    source_path: &str,
    source: &'a str,
    source_language: Option<&str>,
) -> Cow<'a, str> {
    if is_vue_source(source_path, source_language) {
        Cow::Owned(project_vue_sfc_script_to_typescript_source(source))
    } else {
        Cow::Borrowed(source)
    }
}

pub(crate) fn source_type_for_language(
    source_path: &str,
    source_language: Option<&str>,
) -> SourceType {
    if is_vue_source(source_path, source_language) {
        return SourceType::tsx();
    }
    SourceType::from_path(source_path).unwrap_or_else(|_| SourceType::tsx())
}

fn project_vue_sfc_script_to_typescript_source(source: &str) -> String {
    let lower = source.to_ascii_lowercase();
    let mut keep = vec![false; source.len()];
    let mut cursor = 0usize;

    while let Some(relative_start) = lower[cursor..].find("<script") {
        let tag_start = cursor + relative_start;
        let Some(relative_tag_end) = lower[tag_start..].find('>') else {
            break;
        };
        let content_start = tag_start + relative_tag_end + 1;
        let Some(relative_close_start) = lower[content_start..].find("</script>") else {
            break;
        };
        let content_end = content_start + relative_close_start;
        for item in keep.iter_mut().take(content_end).skip(content_start) {
            *item = true;
        }
        cursor = content_end + "</script>".len();
    }

    let mut projected = String::with_capacity(source.len());
    for (index, ch) in source.char_indices() {
        if ch == '\n' {
            projected.push('\n');
        } else if keep[index] {
            projected.push(ch);
        } else {
            for _ in 0..ch.len_utf8() {
                projected.push(' ');
            }
        }
    }
    projected
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn vue_sfc_projection_preserves_byte_offsets_and_script_text() {
        let source = "<template>한글</template>\n<script setup lang=\"ts\">\nconst styles = useCssModule();\n</script>\n<style module>.root {}</style>\n";
        let projected = project_vue_sfc_script_to_typescript_source(source);

        assert_eq!(projected.len(), source.len());
        assert_eq!(
            projected.find("styles = useCssModule"),
            source.find("styles = useCssModule")
        );
        assert!(!projected.contains("한글"));
        assert!(!projected.contains(".root"));
    }
}