use brink_syntax_native::ast::{self, AstNode as _};
use rowan::TextRange;
use crate::hir::FileId;
use crate::{Diagnostic, DiagnosticCode, Import, ImportItem};
use super::SyntaxToken;
use super::decl::joined_path_text;
use super::expr::lower_path;
fn diag(file: FileId, range: TextRange, code: DiagnosticCode) -> Diagnostic {
Diagnostic {
file,
range,
message: code.title().to_string(),
code,
}
}
fn module_name(segments: &[SyntaxToken]) -> String {
segments
.iter()
.map(rowan::SyntaxToken::text)
.collect::<Vec<_>>()
.join("::")
}
fn span_of(segments: &[SyntaxToken]) -> Option<TextRange> {
let first = segments.first()?;
let last = segments.last()?;
Some(TextRange::new(
first.text_range().start(),
last.text_range().end(),
))
}
pub(super) fn lower_use_decl(
file_id: FileId,
node: &ast::UseDecl,
diags: &mut Vec<Diagnostic>,
) -> Option<Import> {
let range = node.syntax().text_range();
let Some(tree) = node.tree() else {
diags.push(diag(file_id, range, DiagnosticCode::E012));
return None;
};
let segments: Vec<SyntaxToken> = tree.path_segments().collect();
let Some(module_range) = span_of(&segments) else {
diags.push(diag(file_id, range, DiagnosticCode::E012));
return None;
};
if let Some(list) = tree.nested_list() {
if tree.alias_token().is_some() {
diags.push(diag(file_id, range, DiagnosticCode::E129));
return None;
}
let mut items = Vec::new();
let mut ok = true;
for item_tree in list.trees() {
if item_tree.nested_list().is_some() {
diags.push(diag(
file_id,
item_tree.syntax().text_range(),
DiagnosticCode::E129,
));
ok = false;
continue;
}
let segs: Vec<String> = item_tree
.path_segments()
.map(|t| t.text().to_string())
.collect();
if segs.len() != 1 {
diags.push(diag(
file_id,
item_tree.syntax().text_range(),
DiagnosticCode::E129,
));
ok = false;
continue;
}
let alias = item_tree.alias_token().map(|t| t.text().to_string());
items.push(ImportItem {
name: segs[0].clone(),
alias,
range: item_tree.syntax().text_range(),
});
}
if !ok && items.is_empty() {
return None;
}
return Some(Import {
module: module_name(&segments),
module_range,
items,
bare: true,
range,
});
}
let Some((leaf, prefix)) = segments.split_last() else {
diags.push(diag(file_id, range, DiagnosticCode::E012));
return None;
};
if prefix.is_empty() {
if tree.alias_token().is_some() {
diags.push(diag(file_id, range, DiagnosticCode::E129));
return None;
}
return Some(Import {
module: leaf.text().to_string(),
module_range,
items: Vec::new(),
bare: false,
range,
});
}
let alias_token = tree.alias_token();
let item_range = alias_token.as_ref().map_or_else(
|| leaf.text_range(),
|alias| TextRange::new(leaf.text_range().start(), alias.text_range().end()),
);
Some(Import {
module: module_name(prefix),
module_range: span_of(prefix).unwrap_or(module_range),
items: vec![ImportItem {
name: leaf.text().to_string(),
alias: alias_token.map(|t| t.text().to_string()),
range: item_range,
}],
bare: true,
range,
})
}
pub(super) fn lower_import_decl(
file_id: FileId,
node: &ast::ImportDecl,
diags: &mut Vec<Diagnostic>,
) -> Option<Import> {
let range = node.syntax().text_range();
let Some(path) = node.path() else {
diags.push(diag(file_id, range, DiagnosticCode::E012));
return None;
};
let module_name = joined_path_text(&path);
if module_name.is_empty() {
diags.push(diag(file_id, range, DiagnosticCode::E012));
return None;
}
let module_range = lower_path(&path).range;
Some(Import {
module: module_name,
module_range,
items: Vec::new(),
bare: false,
range,
})
}