rulewright 0.1.2

Executable engineering standards for Rust workspaces
Documentation
use ra_ap_syntax::{
    AstNode,
    ast::{self, HasAttrs, HasDocComments, LiteralKind},
};

pub(super) fn is_item_or_impl_fn(function: &ast::Fn) -> bool {
    !function.syntax().ancestors().skip(1).any(|ancestor| {
        ast::Trait::can_cast(ancestor.kind()) || ast::ExternBlock::can_cast(ancestor.kind())
    })
}

pub(super) fn doc_lines(function: &ast::Fn) -> Vec<String> {
    let mut lines: Vec<String> = function
        .doc_comments()
        .filter_map(|comment| comment.doc_comment().map(|(text, _)| text.to_owned()))
        .collect();

    lines.extend(function.attrs().filter_map(|attr| doc_attr_text(&attr)));

    lines
}

fn doc_attr_text(attr: &ast::Attr) -> Option<String> {
    let ast::Meta::KeyValueMeta(meta) = attr.meta()? else {
        return None;
    };

    let name = meta
        .path()
        .and_then(|path| path.segment())
        .and_then(|segment| segment.name_ref());

    if name.is_none_or(|name| name.text() != "doc") {
        return None;
    }

    let ast::Expr::Literal(literal) = meta.expr()? else {
        return None;
    };
    let LiteralKind::String(text) = literal.kind() else {
        return None;
    };

    text.value().ok().map(std::borrow::Cow::into_owned)
}

pub(super) fn has_heading(doc_lines: &[String], name: &str) -> bool {
    doc_lines
        .iter()
        .flat_map(|chunk| chunk.lines())
        .any(|line| {
            let trimmed = line.trim();
            let stripped = trimmed.trim_start_matches('#');

            stripped.len() < trimmed.len() && stripped.trim() == name
        })
}