fluidattacks-blends-domain 0.40.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use crate::syntax::fields::php::{namespace_use_clause, namespace_use_declaration};
use crate::{
    query::{adj_ast, label_text, match_ast_d, match_ast_group_d},
    syntax::{
        builders::import_statement::{build_import_statement_node, ImportElement},
        SyntaxGraphArgs, SyntaxGraphError,
    },
    utilities::text_nodes::node_to_str,
    NodeId,
};
use alloc::{format, string::String, vec::Vec};

pub fn reader(
    args: &mut SyntaxGraphArgs<'_>,
    n_id: NodeId,
) -> Result<Option<NodeId>, SyntaxGraphError> {
    let graph = args.ast_graph;

    // A grouped import keeps its clauses inside braces and writes their shared
    // prefix once, before them.
    let prefix = match_ast_d(graph, n_id, "namespace_name", None)
        .map(|prefix_n_id| node_to_str(graph, prefix_n_id));
    let clauses_parent = args
        .optional_field_alt(n_id, namespace_use_declaration::BODY)
        .unwrap_or(n_id);
    let typed_statement = args
        .optional_field_alt(n_id, namespace_use_declaration::TYPE)
        .is_some();

    let elements: Vec<ImportElement> =
        match_ast_group_d(graph, clauses_parent, "namespace_use_clause", None)
            .into_iter()
            .filter_map(|clause_n_id| {
                clause_element(args, clause_n_id, prefix.as_deref(), typed_statement)
            })
            .collect();

    build_import_statement_node(args, n_id, &elements, false, &[]).map(Some)
}

fn clause_element(
    args: &SyntaxGraphArgs<'_>,
    clause_n_id: NodeId,
    prefix: Option<&str>,
    typed_statement: bool,
) -> Option<ImportElement> {
    let graph = args.ast_graph;
    let alias_n_id = args.optional_field_alt(clause_n_id, namespace_use_clause::ALIAS);

    // The imported name is the first name that is not the alias; the
    // `function` or `const` keyword before it is not a name.
    let name_n_id = adj_ast(graph, clause_n_id, None, &["name", "qualified_name"])
        .into_iter()
        .find(|child_n_id| Some(*child_n_id) != alias_n_id)?;
    let name = node_to_str(graph, name_n_id);
    let qualified = prefix.map_or_else(|| name.clone(), |prefix| format!("{prefix}\\{name}"));
    // A use always names from the global namespace, so a leading backslash
    // is redundant and is dropped for the name to read the same either way.
    let expression = qualified
        .strip_prefix('\\')
        .map_or_else(|| qualified.clone(), String::from);

    let alias = alias_n_id
        .and_then(|alias_n_id| label_text(graph, alias_n_id))
        .map(String::from);
    // PHP keeps functions and constants apart from variables, but the graph
    // drops the `$` of a variable, so binding their names would shadow a
    // same-named variable in the file scope.
    let typed_clause = typed_statement
        || args
            .optional_field_alt(clause_n_id, namespace_use_clause::TYPE)
            .is_some();
    let bound_name = if typed_clause {
        String::new()
    } else {
        alias
            .clone()
            .unwrap_or_else(|| String::from(expression.rsplit('\\').next().unwrap_or(&expression)))
    };

    Some(ImportElement {
        expression: Some(expression),
        alias,
        corrected_n_id: Some(clause_n_id),
        bound_name: Some(bound_name),
        ..ImportElement::default()
    })
}