harn-cli 0.10.53

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use std::path::Path;

use harn_parser::{DiagnosticCode as Code, Node, SNode};

use super::PreflightDiagnostic;
use crate::commands::check::host_capabilities::HostCapabilities;

pub(in crate::commands::check) fn host_render_path_arg(arg: Option<&SNode>) -> Option<String> {
    let Node::DictLiteral(entries) = &arg?.node else {
        return None;
    };
    entries
        .iter()
        .find_map(|entry| match (&entry.key.node, &entry.value.node) {
            (Node::Identifier(key), Node::StringLiteral(path)) if key == "path" => {
                Some(path.clone())
            }
            (Node::StringLiteral(key), Node::StringLiteral(path)) if key == "path" => {
                Some(path.clone())
            }
            _ => None,
        })
}

pub(in crate::commands::check) fn parse_host_call_args(
    args: &[SNode],
) -> Option<(String, String, Option<&SNode>)> {
    let Node::StringLiteral(name) = &args.first()?.node else {
        return None;
    };
    let (capability, operation) = name.split_once('.')?;
    Some((capability.to_string(), operation.to_string(), args.get(1)))
}

#[allow(clippy::too_many_arguments)]
pub(super) fn scan_host_param_discriminators(
    call_node: &SNode,
    params_arg: Option<&SNode>,
    host_capabilities: &HostCapabilities,
    capability: &str,
    operation: &str,
    file_path: &Path,
    source: &str,
    diagnostics: &mut Vec<PreflightDiagnostic>,
) {
    let Some(discriminators) = host_capabilities.param_discriminators(capability, operation) else {
        return;
    };
    let entries = params_arg.and_then(|arg| match &arg.node {
        Node::DictLiteral(entries) => Some(entries.as_slice()),
        _ => None,
    });
    for (field, policy) in discriminators {
        let field_entry = entries.and_then(|entries| {
            entries.iter().find(|entry| match &entry.key.node {
                Node::Identifier(key) | Node::StringLiteral(key) => key == field,
                _ => false,
            })
        });
        if let Some(entry) = field_entry {
            if let Node::StringLiteral(value) = &entry.value.node {
                if !policy.allowed_values.contains(value) {
                    diagnostics.push(PreflightDiagnostic {
                        code: Code::CapabilityUnknownOperation,
                        path: file_path.display().to_string(),
                        source: source.to_string(),
                        span: entry.value.span,
                        message: format!(
                            "preflight: unknown `{field}` discriminator '{value}' for host capability/operation '{capability}.{operation}'"
                        ),
                        help: Some(format!(
                            "use one of the host-declared literal values: {}",
                            policy
                                .allowed_values
                                .iter()
                                .cloned()
                                .collect::<Vec<_>>()
                                .join(", ")
                        )),
                        tags: Some(format!("{capability}.{operation}")),
                    });
                }
                continue;
            }
            if policy.allow_dynamic {
                continue;
            }
        }
        diagnostics.push(PreflightDiagnostic {
            code: Code::CapabilityCallStaticNameRequired,
            path: file_path.display().to_string(),
            source: source.to_string(),
            span: field_entry
                .map(|entry| entry.value.span)
                .or_else(|| params_arg.map(|arg| arg.span))
                .unwrap_or(call_node.span),
            message: format!(
                "preflight: host_call(\"{capability}.{operation}\", ...) requires a literal `{field}` params field for static validation"
            ),
            help: Some(format!(
                "set `{field}` to one of the host-declared literal values: {}",
                policy
                    .allowed_values
                    .iter()
                    .cloned()
                    .collect::<Vec<_>>()
                    .join(", ")
            )),
            tags: Some(format!("{capability}.{operation}")),
        });
    }
}