use crate::ast::{Declaration, FieldType, Schema};
use crate::span::Span;
use crate::token::{Token, TokenKind};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticKind {
ModelRef,
EnumRef,
CompositeTypeRef,
}
#[derive(Debug, Clone)]
pub struct SemanticToken {
pub span: Span,
pub kind: SemanticKind,
}
pub fn semantic_tokens(ast: &Schema, tokens: &[Token]) -> Vec<SemanticToken> {
let model_names: HashSet<&str> = ast.models().map(|m| m.name.value.as_str()).collect();
let enum_names: HashSet<&str> = ast.enums().map(|e| e.name.value.as_str()).collect();
let type_names: HashSet<&str> = ast.types().map(|t| t.name.value.as_str()).collect();
let mut result = Vec::new();
for decl in &ast.declarations {
match decl {
Declaration::Model(model) => {
collect_field_tokens(
&model.fields,
tokens,
&model_names,
&enum_names,
&type_names,
&mut result,
);
}
Declaration::Type(type_decl) => {
collect_field_tokens(
&type_decl.fields,
tokens,
&model_names,
&enum_names,
&type_names,
&mut result,
);
}
_ => {}
}
}
result.sort_by_key(|t| t.span.start);
result
}
fn collect_field_tokens(
fields: &[crate::ast::FieldDecl],
tokens: &[Token],
model_names: &HashSet<&str>,
enum_names: &HashSet<&str>,
type_names: &HashSet<&str>,
result: &mut Vec<SemanticToken>,
) {
for field in fields {
if let FieldType::UserType(type_name) = &field.field_type {
let kind = if type_names.contains(type_name.as_str()) {
SemanticKind::CompositeTypeRef
} else if model_names.contains(type_name.as_str()) {
SemanticKind::ModelRef
} else if enum_names.contains(type_name.as_str()) {
SemanticKind::EnumRef
} else {
continue;
};
if let Some(tok) = tokens.iter().find(|t| {
matches!(&t.kind, TokenKind::Ident(name) if name == type_name)
&& t.span.start >= field.span.start
&& t.span.end <= field.span.end
&& t.span.start != field.name.span.start
}) {
result.push(SemanticToken {
span: tok.span,
kind,
});
}
}
}
}