fluidattacks-blends-domain 0.28.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use alloc::vec;
use alloc::vec::Vec;

use crate::query::{adj_ast, match_ast_group_d};
use crate::{CodeGraph, NodeId};

/// The Go types whose composite literal is a list of its elements.
pub const ARRAY_TYPES: &[&str] = &["array_type", "implicit_length_array_type", "slice_type"];

#[must_use]
pub fn pair_declaration_value_ids<G: CodeGraph>(
    graph: &G,
    value_field_id: Option<NodeId>,
    count: usize,
) -> Vec<Option<NodeId>> {
    let Some(value_field_id) = value_field_id else {
        return vec![None; count];
    };

    let exprs: Vec<NodeId> = adj_ast(graph, value_field_id, Some(1), &[])
        .into_iter()
        .filter(|c_id| graph.label_type(*c_id) != Some(","))
        .collect();
    if exprs.len() == count {
        return exprs.into_iter().map(Some).collect();
    }

    vec![Some(unwrap_single_expression_list(graph, value_field_id)); count]
}

/// The single expression of a one-element `expression_list`, else `id` itself.
#[must_use]
pub fn unwrap_single_expression_list<G: CodeGraph>(graph: &G, id: NodeId) -> NodeId {
    if graph.label_type(id) == Some("expression_list") {
        if let [only] = *adj_ast(graph, id, None, &[]).as_slice() {
            return only;
        }
    }
    id
}

fn version_digits(segment: &str) -> Option<&str> {
    segment
        .strip_prefix('v')
        .filter(|digits| !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()))
}

// gopkg.in paths carry the version after a dot, at any major.
fn is_dot_version(segment: &str) -> bool {
    version_digits(segment).is_some()
}

// A slash version segment exists from v2 on; v0 and v1 are regular packages.
fn is_path_major_version(segment: &str) -> bool {
    version_digits(segment).is_some_and(|digits| {
        let digits = digits.trim_start_matches('0');
        digits.len() > 1 || digits.as_bytes().first().is_some_and(|byte| *byte >= b'2')
    })
}

fn strip_go_decoration(name: &str) -> &str {
    let name = name.strip_prefix("go-").unwrap_or(name);
    let name = name.strip_suffix("-go").unwrap_or(name);
    name.strip_suffix(".go").unwrap_or(name)
}

/// The package name a Go import path binds: its last segment, skipping a
/// major version suffix and the conventional `go-`, `-go` and `.go`
/// decorations.
#[must_use]
pub fn package_name(path: &str) -> &str {
    let mut segments = path.rsplit('/').filter(|segment| !segment.is_empty());
    let mut name = match segments.next() {
        Some(last) if is_path_major_version(last) => segments.next().unwrap_or(last),
        Some(last) => last,
        None => return "",
    };
    if let Some((stem, suffix)) = name.rsplit_once('.') {
        if is_dot_version(suffix) {
            name = stem;
        }
    }
    strip_go_decoration(name)
}

/// Every element of a `literal_value` without keyed elements. The element reader
/// hands back its own expression unless the element omits a record type.
#[must_use]
pub fn literal_elements<G: CodeGraph>(graph: &G, literal_value_id: NodeId) -> Vec<NodeId> {
    match_ast_group_d(graph, literal_value_id, "literal_element", None)
}