perl-parser 0.13.3

Native Perl parser (v3) — recursive descent with Tree-sitter-compatible AST, semantic analysis, and LSP provider engine
Documentation
use crate::heredoc_anti_patterns::model::Location;

pub(crate) fn build_line_starts(code: &str) -> Vec<usize> {
    let mut line_starts = Vec::new();
    line_starts.push(0);

    for (idx, byte) in code.bytes().enumerate() {
        if byte == b'\n' {
            line_starts.push(idx + 1);
        }
    }

    line_starts
}

pub(crate) fn location_from_start(line_starts: &[usize], offset: usize, start: usize) -> Location {
    let insertion = line_starts.partition_point(|&line_start| line_start <= start);
    let line = insertion.saturating_sub(1);
    let line_start = line_starts.get(line).copied().unwrap_or(0);
    let column = start.saturating_sub(line_start);

    Location { line, column, offset: offset + start }
}

pub(crate) fn mask_non_code_regions(code: &str) -> String {
    fn push_masked_char(masked: &mut String, ch: char) {
        for _ in 0..ch.len_utf8() {
            masked.push(' ');
        }
    }

    let mut masked = String::with_capacity(code.len());
    let mut in_single_quote = false;
    let mut in_double_quote = false;
    let mut in_line_comment = false;
    let mut escaped = false;

    for ch in code.chars() {
        if in_line_comment {
            if ch == '\n' {
                in_line_comment = false;
                masked.push('\n');
            } else {
                push_masked_char(&mut masked, ch);
            }
            continue;
        }

        if in_single_quote {
            if escaped {
                escaped = false;
            } else if ch == '\\' {
                escaped = true;
            } else if ch == '\'' {
                in_single_quote = false;
            }
            push_masked_char(&mut masked, ch);
            continue;
        }

        if in_double_quote {
            if escaped {
                escaped = false;
            } else if ch == '\\' {
                escaped = true;
            } else if ch == '"' {
                in_double_quote = false;
            }
            push_masked_char(&mut masked, ch);
            continue;
        }

        match ch {
            '#' => {
                in_line_comment = true;
                push_masked_char(&mut masked, ch);
            }
            '\'' => {
                in_single_quote = true;
                push_masked_char(&mut masked, ch);
            }
            '"' => {
                in_double_quote = true;
                push_masked_char(&mut masked, ch);
            }
            _ => masked.push(ch),
        }
    }

    masked
}