use crate::{
LanguageService, LintLevel,
cfa::{self, FlowNodeKind},
document::Document,
helpers,
};
use line_index::LineIndex;
use lspt::{Diagnostic, DiagnosticSeverity, DiagnosticTag, Union2};
use rowan::{
TextRange,
ast::{AstNode, SyntaxNodePtr, support},
};
use wat_syntax::{SyntaxKind, SyntaxNode, ast::Instr};
const DIAGNOSTIC_CODE: &str = "unreachable";
pub fn check(
diagnostics: &mut Vec<Diagnostic>,
service: &LanguageService,
document: Document,
lint_level: LintLevel,
line_index: &LineIndex,
root: &SyntaxNode,
node: &SyntaxNode,
) {
let severity = match lint_level {
LintLevel::Allow => return,
LintLevel::Hint => DiagnosticSeverity::Hint,
LintLevel::Warn => DiagnosticSeverity::Warning,
LintLevel::Deny => DiagnosticSeverity::Error,
};
let cfg = cfa::analyze(service, document, SyntaxNodePtr::new(node));
let mut ranges = Vec::<TextRange>::new();
cfg.graph.raw_nodes().iter().for_each(|node| {
if !node.weight.unreachable {
return;
}
match &node.weight.kind {
FlowNodeKind::BasicBlock(bb) => {
bb.instrs(root).for_each(|instr| {
let current = instr.text_range();
if let Some(last) = ranges.last_mut() {
if instr
.prev_sibling()
.is_some_and(|prev| last.contains_range(prev.text_range()))
{
*last = last.cover(current);
} else if current.contains_range(*last) {
if instr
.first_child_by_kind(&Instr::can_cast)
.is_none_or(|first| last.contains_range(first.text_range()))
{
*last = current;
} else if let Some(instr_name) =
support::token(&instr, SyntaxKind::INSTR_NAME)
{
ranges.push(instr_name.text_range());
}
} else if !last.contains_range(current) {
ranges.push(current);
}
} else if instr.children().any(|child| Instr::can_cast(child.kind()))
&& let Some(instr_name) = support::token(&instr, SyntaxKind::INSTR_NAME)
{
ranges.push(instr_name.text_range());
} else {
ranges.push(current);
}
});
}
FlowNodeKind::BlockEntry(entry) => {
let node = entry.to_node(root);
if let Some((prev, last)) = node.prev_sibling().zip(ranges.last_mut())
&& last.contains_range(prev.text_range())
{
*last = last.cover(node.text_range());
} else {
ranges.push(node.text_range());
}
}
_ => {}
}
});
diagnostics.extend(ranges.into_iter().map(|range| Diagnostic {
range: helpers::rowan_range_to_lsp_range(line_index, range),
severity: Some(severity),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: "unreachable code".into(),
tags: Some(vec![DiagnosticTag::Unnecessary]),
..Default::default()
}));
}