rototo 0.1.0-alpha.8

Control plane for runtime configuration of your application.
Documentation
use crate::diagnostics::{DiagnosticLocation, SourcePosition};

use super::super::PackageLintSnapshot;
use super::super::index::*;
use super::PackageDefinition;
use super::common::{
    location_contains_position, source_range_size, variable_value_definition_location,
};

pub(crate) fn definition(
    snapshot: &PackageLintSnapshot,
    path: &str,
    position: SourcePosition,
) -> Option<PackageDefinition> {
    let mut candidates = Vec::new();
    push_reference_definition_candidates(snapshot, path, position, &mut candidates);
    push_variable_definition_candidates(&snapshot.index, path, position, &mut candidates);
    sort_definition_candidates(&mut candidates);
    candidates
        .into_iter()
        .next()
        .and_then(|candidate| definition_for_location(snapshot, candidate.location))
}

fn definition_for_location(
    snapshot: &PackageLintSnapshot,
    mut location: DiagnosticLocation,
) -> Option<PackageDefinition> {
    let document = snapshot
        .lint
        .documents
        .iter()
        .find(|document| document.path == location.path)?;
    location.doc = Some(document.id);
    let uri = document.uri.clone();
    Some(PackageDefinition { uri, location })
}

fn push_reference_definition_candidates(
    snapshot: &PackageLintSnapshot,
    path: &str,
    position: SourcePosition,
    candidates: &mut Vec<DefinitionCandidate>,
) {
    if let Some(target) = snapshot.references.target_at_position(path, position)
        && let Some(location) = snapshot.references.declaration(&target)
    {
        candidates.push(DefinitionCandidate {
            priority: 0,
            span_size: location.range.map(source_range_size).unwrap_or(usize::MAX),
            location,
        });
    }
}

struct DefinitionCandidate {
    priority: u8,
    span_size: usize,
    location: DiagnosticLocation,
}

fn push_variable_definition_candidates(
    index: &SemanticIndex,
    path: &str,
    position: SourcePosition,
    candidates: &mut Vec<DefinitionCandidate>,
) {
    for variable in index.variables.values() {
        match &variable.type_source {
            TypeSourceNode::Catalog(catalog)
                if location_contains_position(&catalog.location, path, position) =>
            {
                if let Some(catalog_node) = index.catalogs.get(&catalog.value) {
                    candidates.push(DefinitionCandidate {
                        priority: 1,
                        span_size: catalog
                            .location
                            .range
                            .map(source_range_size)
                            .unwrap_or(usize::MAX),
                        location: catalog_node.location.clone(),
                    });
                }
            }
            _ => {}
        }

        let ResolveNode::Resolve { default, rules, .. } = &variable.resolve else {
            continue;
        };

        if let ProjectField::Present(value) = default.as_ref()
            && location_contains_position(&value.location, path, position)
            && let Some(target) = variable_value_definition_location(index, variable, &value.value)
        {
            candidates.push(DefinitionCandidate {
                priority: 0,
                span_size: value
                    .location
                    .range
                    .map(source_range_size)
                    .unwrap_or(usize::MAX),
                location: target,
            });
        }

        let RuleCollection::Rules(rules) = rules else {
            continue;
        };

        for rule in rules {
            if let ProjectField::Present(value) = &rule.value
                && location_contains_position(&value.location, path, position)
                && let Some(target) =
                    variable_value_definition_location(index, variable, &value.value)
            {
                candidates.push(DefinitionCandidate {
                    priority: 0,
                    span_size: value
                        .location
                        .range
                        .map(source_range_size)
                        .unwrap_or(usize::MAX),
                    location: target,
                });
            }
        }
    }
}

fn sort_definition_candidates(candidates: &mut [DefinitionCandidate]) {
    candidates.sort_by(|left, right| {
        left.priority
            .cmp(&right.priority)
            .then_with(|| left.span_size.cmp(&right.span_size))
            .then_with(|| left.location.path.cmp(&right.location.path))
    });
}