kconfig-lsp 0.1.0

A language server for Kconfig files used in Linux, Zephyr, U-Boot, and coreboot
Documentation
use std::path::Path;

use tower_lsp::lsp_types::{self as lsp, DiagnosticSeverity};

use crate::analysis::WorldIndex;
use crate::ast::DiagSeverity;

pub fn collect(index: &WorldIndex, path: &Path) -> Vec<lsp::Diagnostic> {
    let fa = match index.files.get(path) {
        Some(fa) => fa,
        None => return Vec::new(),
    };

    let mut diags: Vec<lsp::Diagnostic> = Vec::new();

    for pd in &fa.diagnostics {
        let (line, col) = fa.line_index.line_col(pd.span.start);
        let (end_line, end_col) = fa.line_index.line_col(pd.span.end);
        diags.push(lsp::Diagnostic {
            range: lsp::Range {
                start: lsp::Position::new(line, col),
                end: lsp::Position::new(end_line, end_col),
            },
            severity: Some(match pd.severity {
                DiagSeverity::Error => DiagnosticSeverity::ERROR,
                DiagSeverity::Warning => DiagnosticSeverity::WARNING,
            }),
            source: Some("kconfig-lsp".into()),
            message: pd.message.clone(),
            ..Default::default()
        });
    }

    for ref_entry in index.references.values() {
        for r in ref_entry {
            if r.file != path {
                continue;
            }
            if index.get_definitions(&r.name).is_empty()
                && !is_well_known_symbol(&r.name)
                && !r.name.starts_with("$(")
            {
                let (line, col) = fa.line_index.line_col(r.span.start);
                let (end_line, end_col) = fa.line_index.line_col(r.span.end);
                diags.push(lsp::Diagnostic {
                    range: lsp::Range {
                        start: lsp::Position::new(line, col),
                        end: lsp::Position::new(end_line, end_col),
                    },
                    severity: Some(DiagnosticSeverity::WARNING),
                    source: Some("kconfig-lsp".into()),
                    message: format!("symbol `{}` is not defined in any open file", r.name),
                    ..Default::default()
                });
            }
        }
    }

    diags
}

fn is_well_known_symbol(name: &str) -> bool {
    matches!(
        name,
        "y" | "n"
            | "m"
            | "MODULES"
            | "COMPILE_TEST"
            | "EXPERT"
            | "NET"
            | "BLOCK"
            | "SMP"
            | "PCI"
            | "USB"
            | "HAS_IOMEM"
            | "HAS_DMA"
            | "MMU"
            | "OF"
            | "ACPI"
            | "PM"
            | "ARCH_HAS_DMA_PREP_COHERENT"
    )
}