tokmd-analysis 1.13.0

Analysis logic and enrichers for tokmd receipts.
Documentation
pub(in crate::complexity) fn detect_fn_spans_python(lines: &[&str]) -> Vec<(usize, usize, String)> {
    let mut spans = Vec::new();
    let mut i = 0;
    while i < lines.len() {
        let trimmed = lines[i].trim();
        if trimmed.starts_with("def ") || trimmed.starts_with("async def ") {
            let name = extract_python_fn_name(trimmed);
            let base_indent = lines[i].len() - lines[i].trim_start().len();

            let mut start = i;
            {
                let mut k = i;
                while k > 0 {
                    let prev_line = lines[k - 1];
                    let prev_trimmed = prev_line.trim();

                    if prev_trimmed.is_empty() {
                        k -= 1;
                        continue;
                    }

                    if prev_trimmed.starts_with('#') {
                        k -= 1;
                        continue;
                    }

                    let prev_indent = prev_line.len() - prev_line.trim_start().len();
                    if prev_indent == base_indent && prev_trimmed.starts_with('@') {
                        start = k - 1;
                        k -= 1;
                    } else {
                        break;
                    }
                }
            }
            let mut end = i;
            let mut j = i + 1;
            while j < lines.len() {
                let lt = lines[j].trim();
                if lt.is_empty() || lt.starts_with('#') {
                    j += 1;
                    continue;
                }
                let indent = lines[j].len() - lines[j].trim_start().len();
                if indent <= base_indent {
                    break;
                }
                end = j;
                j += 1;
            }
            spans.push((start, end, name));
            i = end + 1;
        } else {
            i += 1;
        }
    }
    spans
}

fn extract_python_fn_name(line: &str) -> String {
    let keyword = if line.contains("async def ") {
        "async def "
    } else {
        "def "
    };
    if let Some(idx) = line.find(keyword) {
        let after = &line[idx + keyword.len()..];
        let name: String = after
            .chars()
            .take_while(|c| c.is_alphanumeric() || *c == '_')
            .collect();
        if !name.is_empty() {
            return name;
        }
    }
    "<unknown>".to_string()
}