rototo 0.1.0-alpha.4

Control plane for runtime configuration of your application.
Documentation
use super::super::index::*;
use super::common::{predicate_op_project_field_value, string_project_field_value};
use super::{WorkspaceDocumentSymbol, WorkspaceDocumentSymbolKind};

pub(crate) fn document_symbols(index: &SemanticIndex, path: &str) -> Vec<WorkspaceDocumentSymbol> {
    let mut symbols = Vec::new();

    if let Some(manifest) = &index.manifest
        && manifest.location.path == path
        && let Some(symbol) = workspace_extends_symbol(&manifest.extends)
    {
        symbols.push(symbol);
    }

    for qualifier in index.qualifiers.values() {
        if qualifier.location.path == path {
            symbols.push(qualifier_document_symbol(qualifier));
        }
    }

    for variable in index.variables.values() {
        if variable.location.path == path {
            symbols.push(variable_document_symbol(variable));
        }
    }

    for resource in index.resources.values() {
        if resource.location.path == path {
            symbols.push(resource_document_symbol(resource));
        }
    }

    for objects in index.resource_objects.values() {
        for object in objects.values() {
            if object.location.path == path {
                symbols.push(resource_object_document_symbol(object));
            }
        }
    }

    sort_workspace_document_symbols(&mut symbols);
    symbols
}

fn workspace_extends_symbol(
    extends: &WorkspaceExtendsCollection,
) -> Option<WorkspaceDocumentSymbol> {
    match extends {
        WorkspaceExtendsCollection::Missing => None,
        WorkspaceExtendsCollection::Invalid { location } => Some(WorkspaceDocumentSymbol::new(
            "extends",
            WorkspaceDocumentSymbolKind::WorkspaceExtends,
            location.clone(),
            Vec::new(),
        )),
        WorkspaceExtendsCollection::Sources { location, values } => {
            Some(WorkspaceDocumentSymbol::new(
                "extends",
                WorkspaceDocumentSymbolKind::WorkspaceExtends,
                location.clone(),
                values
                    .iter()
                    .map(|source| {
                        WorkspaceDocumentSymbol::new(
                            source.source.clone(),
                            WorkspaceDocumentSymbolKind::WorkspaceExtendSource,
                            source.location.clone(),
                            Vec::new(),
                        )
                    })
                    .collect(),
            ))
        }
    }
}

fn qualifier_document_symbol(qualifier: &QualifierNode) -> WorkspaceDocumentSymbol {
    let children = match &qualifier.predicates {
        PredicateCollection::Predicates(predicates) => predicates
            .iter()
            .map(predicate_document_symbol)
            .collect::<Vec<_>>(),
        PredicateCollection::Missing { .. } | PredicateCollection::Invalid { .. } => Vec::new(),
    };
    WorkspaceDocumentSymbol::new(
        qualifier.id.clone(),
        WorkspaceDocumentSymbolKind::Qualifier,
        qualifier.location.clone(),
        children,
    )
}

fn predicate_document_symbol(predicate: &PredicateNode) -> WorkspaceDocumentSymbol {
    WorkspaceDocumentSymbol::new(
        predicate_symbol_name(predicate),
        WorkspaceDocumentSymbolKind::Predicate,
        predicate.location.clone(),
        Vec::new(),
    )
}

fn variable_document_symbol(variable: &VariableNode) -> WorkspaceDocumentSymbol {
    let mut children = Vec::new();
    if let Some(values) = variable_values_document_symbol(variable) {
        children.push(values);
    }
    if let Some(resolve) = variable_resolve_document_symbol(variable) {
        children.push(resolve);
    }

    WorkspaceDocumentSymbol::new(
        variable.id.clone(),
        WorkspaceDocumentSymbolKind::Variable,
        variable.location.clone(),
        children,
    )
}

fn variable_values_document_symbol(variable: &VariableNode) -> Option<WorkspaceDocumentSymbol> {
    if variable.values.inline_values.is_empty() && !variable.values.invalid_shape {
        return None;
    }

    Some(WorkspaceDocumentSymbol::new(
        "values",
        WorkspaceDocumentSymbolKind::Values,
        variable.values.location.clone(),
        variable
            .values
            .inline_values
            .values()
            .map(value_document_symbol)
            .collect(),
    ))
}

fn variable_resolve_document_symbol(variable: &VariableNode) -> Option<WorkspaceDocumentSymbol> {
    let ResolveNode::Resolve {
        location, rules, ..
    } = &variable.resolve
    else {
        return None;
    };
    let children = match rules {
        RuleCollection::Rules(rules) => rules
            .iter()
            .map(variable_rule_document_symbol)
            .collect::<Vec<_>>(),
        RuleCollection::Invalid { .. } => Vec::new(),
    };
    Some(WorkspaceDocumentSymbol::new(
        "resolve",
        WorkspaceDocumentSymbolKind::Resolve,
        location.clone(),
        children,
    ))
}

fn variable_rule_document_symbol(rule: &VariableRuleNode) -> WorkspaceDocumentSymbol {
    WorkspaceDocumentSymbol::new(
        variable_rule_symbol_name(rule),
        WorkspaceDocumentSymbolKind::Rule,
        rule.location.clone(),
        Vec::new(),
    )
}

fn resource_document_symbol(resource: &ResourceNode) -> WorkspaceDocumentSymbol {
    WorkspaceDocumentSymbol::new(
        resource.id.clone(),
        WorkspaceDocumentSymbolKind::Resource,
        resource.location.clone(),
        Vec::new(),
    )
}

fn resource_object_document_symbol(object: &ResourceObjectNode) -> WorkspaceDocumentSymbol {
    WorkspaceDocumentSymbol::new(
        format!("{}.{}", object.resource_id, object.key),
        WorkspaceDocumentSymbolKind::ResourceObject,
        object.location.clone(),
        Vec::new(),
    )
}

fn value_document_symbol(value: &ValueNode) -> WorkspaceDocumentSymbol {
    WorkspaceDocumentSymbol::new(
        value.key.clone(),
        WorkspaceDocumentSymbolKind::Value,
        value.location.clone(),
        Vec::new(),
    )
}

fn predicate_symbol_name(predicate: &PredicateNode) -> String {
    let index = predicate.index + 1;
    let Some(attribute) = string_project_field_value(&predicate.attribute) else {
        return format!("predicate {index}");
    };
    let Some(op) = predicate_op_project_field_value(&predicate.op) else {
        return format!("predicate {index}: {attribute}");
    };
    format!("predicate {index}: {attribute} {op}")
}

fn variable_rule_symbol_name(rule: &VariableRuleNode) -> String {
    let index = rule.index + 1;
    match (
        string_project_field_value(&rule.qualifier),
        string_project_field_value(&rule.value),
    ) {
        (Some(qualifier), Some(value)) => format!("rule {index}: {qualifier} -> {value}"),
        (Some(qualifier), None) => format!("rule {index}: {qualifier}"),
        (None, Some(value)) => format!("rule {index}: {value}"),
        (None, None) => format!("rule {index}"),
    }
}

fn sort_workspace_document_symbols(symbols: &mut [WorkspaceDocumentSymbol]) {
    for symbol in symbols.iter_mut() {
        sort_workspace_document_symbols(&mut symbol.children);
    }
    symbols.sort_by(|left, right| {
        symbol_position(left)
            .cmp(&symbol_position(right))
            .then_with(|| left.name.cmp(&right.name))
    });
}

fn symbol_position(symbol: &WorkspaceDocumentSymbol) -> (usize, usize) {
    symbol
        .location
        .range
        .map(|range| (range.start.line, range.start.character))
        .unwrap_or((0, 0))
}