wat_service 0.7.0

WebAssembly Text Format language service.
Documentation
use crate::{
    LanguageService,
    binder::{SymbolKey, SymbolTable},
    config::LintLevel,
    helpers,
    uri::InternUri,
};
use line_index::LineIndex;
use lspt::{
    Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, Union2,
};
use rowan::ast::{AstNode, support};
use rustc_hash::FxHashMap;
use wat_syntax::{
    SyntaxNode,
    ast::{Cat, Catch, CatchAll},
};

const DIAGNOSTIC_CODE: &str = "useless-catch";

pub fn check(
    service: &LanguageService,
    diagnostics: &mut Vec<Diagnostic>,
    lint_level: LintLevel,
    uri: InternUri,
    line_index: &LineIndex,
    symbol_table: &SymbolTable,
    node: &SyntaxNode,
) {
    let severity = match lint_level {
        LintLevel::Allow => return,
        LintLevel::Hint => DiagnosticSeverity::Hint,
        LintLevel::Warn => DiagnosticSeverity::Warning,
        LintLevel::Deny => DiagnosticSeverity::Error,
    };
    let mut default_match: Option<CatchAll> = None;
    let mut matches = FxHashMap::<_, Catch>::default();
    support::children::<Cat>(node).for_each(|cat| match cat {
        Cat::Catch(catch) => {
            if let Some(def_key) = catch
                .tag_index()
                .and_then(|index| symbol_table.resolved.get(&SymbolKey::new(index.syntax())))
            {
                let matched = match (matches.get(def_key), &default_match) {
                    (Some(catch), Some(catch_all)) => {
                        let catch = catch.syntax();
                        let catch_all = catch_all.syntax();
                        if catch.text_range().start() < catch_all.text_range().start() {
                            catch
                        } else {
                            catch_all
                        }
                    }
                    (Some(catch), None) => catch.syntax(),
                    (None, Some(catch_all)) => catch_all.syntax(),
                    (None, None) => {
                        matches.insert(def_key, catch);
                        return;
                    }
                };
                diagnostics.push(build_diagnostic(
                    catch.syntax(),
                    matched,
                    line_index,
                    uri,
                    service,
                    severity,
                ));
            }
        }
        Cat::CatchAll(catch_all) => {
            if let Some(matched) = &default_match {
                diagnostics.push(build_diagnostic(
                    catch_all.syntax(),
                    matched.syntax(),
                    line_index,
                    uri,
                    service,
                    severity,
                ));
            } else {
                default_match = Some(catch_all);
            }
        }
    });
}

fn build_diagnostic(
    reported: &SyntaxNode,
    related: &SyntaxNode,
    line_index: &LineIndex,
    uri: InternUri,
    service: &LanguageService,
    severity: DiagnosticSeverity,
) -> Diagnostic {
    Diagnostic {
        range: helpers::rowan_range_to_lsp_range(line_index, reported.text_range()),
        severity: Some(severity),
        source: Some("wat".into()),
        code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
        message: "this catch clause will never be matched".into(),
        tags: Some(vec![DiagnosticTag::Unnecessary]),
        related_information: Some(vec![DiagnosticRelatedInformation {
            location: Location {
                uri: uri.raw(service),
                range: helpers::rowan_range_to_lsp_range(line_index, related.text_range()),
            },
            message: "catch clause already matched here".into(),
        }]),
        ..Default::default()
    }
}