use super::{
SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, semantic_token_builder::SemanticBuilder,
};
use crate::handlers::semantic_token::escape_sequence_highlight::highlight_string_escapes;
use crate::handlers::semantic_token::function_string_highlight::fun_string_highlight;
use crate::handlers::semantic_token::semantic_token_builder::{
CustomSemanticTokenModifier, CustomSemanticTokenType,
};
use crate::util::parse_desc;
use crate::{context::ClientId, handlers::semantic_token::language_injector::inject_language};
use glua_code_analysis::{
DbIndex, Emmyrc, GlobalId, LocalAttribute, LuaDecl, LuaDeclExtra, LuaMemberFeature,
LuaMemberId, LuaMemberOwner, LuaSemanticDeclId, LuaType, LuaTypeDeclId, LuaTypeOwner,
SemanticDeclLevel, SemanticModel, WorkspaceId, parse_require_module_info,
};
use glua_parser::{
LuaAssignStat, LuaAst, LuaAstNode, LuaAstToken, LuaCallArgList, LuaCallExpr, LuaComment,
LuaDocFieldKey, LuaDocGenericDecl, LuaDocGenericDeclList, LuaDocObjectFieldKey, LuaDocTagOther,
LuaDocType, LuaExpr, LuaGeneralToken, LuaKind, LuaLiteralToken, LuaNameToken, LuaSyntaxKind,
LuaSyntaxNode, LuaSyntaxToken, LuaTokenKind, LuaVarExpr, PathTrait,
};
use glua_parser_desc::{CodeBlockHighlightKind, DescItem, DescItemKind};
use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType};
use rowan::{NodeOrToken, TextRange, TextSize};
use tokio_util::sync::CancellationToken;
pub fn build_semantic_tokens(
semantic_model: &SemanticModel,
support_muliline_token: bool,
client_id: ClientId,
emmyrc: &Emmyrc,
cancel_token: &CancellationToken,
) -> Option<Vec<SemanticToken>> {
let root = semantic_model.get_root();
let document = semantic_model.get_document();
let mut builder = SemanticBuilder::new(
&document,
support_muliline_token,
SEMANTIC_TOKEN_TYPES.to_vec(),
SEMANTIC_TOKEN_MODIFIERS.to_vec(),
);
for node_or_token in root.syntax().descendants_with_tokens() {
if cancel_token.is_cancelled() {
return None;
}
match node_or_token {
NodeOrToken::Node(node) => {
build_node_semantic_token(semantic_model, &mut builder, node, emmyrc);
}
NodeOrToken::Token(token) => {
build_tokens_semantic_token(
semantic_model,
&mut builder,
&token,
client_id,
emmyrc,
);
}
}
}
Some(builder.build())
}
fn build_tokens_semantic_token(
_semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
token: &LuaSyntaxToken,
client_id: ClientId,
emmyrc: &Emmyrc,
) {
if matches!(
token.kind(),
LuaKind::Token(LuaTokenKind::TkName | LuaTokenKind::TkBreak)
) {
let prev_kind = token.prev_token().map(|prev| prev.kind());
let next_kind = token.next_token().map(|next| next.kind());
if prev_kind == Some(LuaKind::Token(LuaTokenKind::TkColon))
&& next_kind == Some(LuaKind::Token(LuaTokenKind::TkColon))
{
builder.push_with_modifier_force(
token,
CustomSemanticTokenType::LABEL,
SemanticTokenModifier::DECLARATION,
);
return;
}
if prev_kind == Some(LuaKind::Token(LuaTokenKind::TkGoto)) {
builder.push_force(token, CustomSemanticTokenType::LABEL);
return;
}
let label_context = token.parent().and_then(|parent| {
parent
.ancestors()
.find(|node| {
node.kind() == LuaSyntaxKind::LabelStat.into()
|| node.kind() == LuaSyntaxKind::GotoStat.into()
})
.map(|node| node.kind())
});
if label_context == Some(LuaSyntaxKind::LabelStat.into()) {
builder.push_with_modifier_force(
token,
CustomSemanticTokenType::LABEL,
SemanticTokenModifier::DECLARATION,
);
return;
}
if label_context == Some(LuaSyntaxKind::GotoStat.into()) {
builder.push_force(token, CustomSemanticTokenType::LABEL);
return;
}
}
match token.kind().into() {
LuaTokenKind::TkLongString => {
if !builder.is_special_string_range(&token.text_range()) {
builder.push(token, SemanticTokenType::STRING);
}
}
LuaTokenKind::TkString => {
if !builder.is_special_string_range(&token.text_range()) {
highlight_string_escapes(builder, token);
}
}
LuaTokenKind::TkAnd
| LuaTokenKind::TkBreak
| LuaTokenKind::TkDo
| LuaTokenKind::TkElse
| LuaTokenKind::TkElseIf
| LuaTokenKind::TkEnd
| LuaTokenKind::TkFor
| LuaTokenKind::TkFunction
| LuaTokenKind::TkGoto
| LuaTokenKind::TkIf
| LuaTokenKind::TkIn
| LuaTokenKind::TkNot
| LuaTokenKind::TkOr
| LuaTokenKind::TkRepeat
| LuaTokenKind::TkReturn
| LuaTokenKind::TkThen
| LuaTokenKind::TkUntil
| LuaTokenKind::TkWhile
| LuaTokenKind::TkGlobal => {
builder.push(token, SemanticTokenType::KEYWORD);
}
LuaTokenKind::TkLocal => {
if !client_id.is_vscode() {
builder.push(token, SemanticTokenType::KEYWORD);
}
}
LuaTokenKind::TkPlus
| LuaTokenKind::TkMinus
| LuaTokenKind::TkMul
| LuaTokenKind::TkDiv
| LuaTokenKind::TkIDiv
| LuaTokenKind::TkShl
| LuaTokenKind::TkShr
| LuaTokenKind::TkBitAnd
| LuaTokenKind::TkBitOr
| LuaTokenKind::TkBitXor
| LuaTokenKind::TkDot
| LuaTokenKind::TkConcat
| LuaTokenKind::TkEq
| LuaTokenKind::TkGe
| LuaTokenKind::TkLe
| LuaTokenKind::TkNe
| LuaTokenKind::TkLt
| LuaTokenKind::TkGt
| LuaTokenKind::TkMod
| LuaTokenKind::TkPow
| LuaTokenKind::TkLen
| LuaTokenKind::TkAssign => {
builder.push(token, SemanticTokenType::OPERATOR);
}
LuaTokenKind::TkLeftBrace | LuaTokenKind::TkRightBrace => {
if let Some(parent) = token.parent()
&& !matches!(
parent.kind().into(),
LuaSyntaxKind::TableArrayExpr
| LuaSyntaxKind::TableEmptyExpr
| LuaSyntaxKind::TableObjectExpr
)
{
builder.push(token, SemanticTokenType::OPERATOR);
}
}
LuaTokenKind::TkColon => {
if let Some(parent) = token.parent()
&& parent.kind() != LuaSyntaxKind::IndexExpr.into()
{
builder.push(token, SemanticTokenType::OPERATOR);
}
}
LuaTokenKind::TkLeftBracket | LuaTokenKind::TkRightBracket => {
if let Some(parent) = token.parent()
&& matches!(
parent.kind().into(),
LuaSyntaxKind::TableFieldAssign | LuaSyntaxKind::IndexExpr
)
{
builder.push(token, CustomSemanticTokenType::DELIMITER);
} else {
builder.push(token, SemanticTokenType::OPERATOR);
}
}
LuaTokenKind::TkLeftParen | LuaTokenKind::TkRightParen => {
if let Some(parent) = token.parent()
&& matches!(
parent.kind().into(),
LuaSyntaxKind::ParamList
| LuaSyntaxKind::CallArgList
| LuaSyntaxKind::ParenExpr
)
{
builder.push(token, CustomSemanticTokenType::DELIMITER);
} else {
builder.push(token, SemanticTokenType::OPERATOR);
}
}
LuaTokenKind::TkTrue | LuaTokenKind::TkFalse | LuaTokenKind::TkNil => {
builder.push_with_modifier(
token,
SemanticTokenType::KEYWORD,
SemanticTokenModifier::READONLY,
);
}
LuaTokenKind::TkComplex | LuaTokenKind::TkInt | LuaTokenKind::TkFloat => {
builder.push(token, SemanticTokenType::NUMBER);
}
LuaTokenKind::TkTagClass
| LuaTokenKind::TkTagEnum
| LuaTokenKind::TkTagInterface
| LuaTokenKind::TkTagAlias
| LuaTokenKind::TkTagModule
| LuaTokenKind::TkTagField
| LuaTokenKind::TkTagType
| LuaTokenKind::TkTagParam
| LuaTokenKind::TkTagReturn
| LuaTokenKind::TkTagOverload
| LuaTokenKind::TkTagGeneric
| LuaTokenKind::TkTagSee
| LuaTokenKind::TkTagDeprecated
| LuaTokenKind::TkTagAsync
| LuaTokenKind::TkTagCast
| LuaTokenKind::TkTagOther
| LuaTokenKind::TkTagReadonly
| LuaTokenKind::TkTagDiagnostic
| LuaTokenKind::TkTagMeta
| LuaTokenKind::TkTagVersion
| LuaTokenKind::TkTagAs
| LuaTokenKind::TkTagNodiscard
| LuaTokenKind::TkTagOperator
| LuaTokenKind::TkTagMapping
| LuaTokenKind::TkTagNamespace
| LuaTokenKind::TkTagUsing
| LuaTokenKind::TkTagSource
| LuaTokenKind::TkTagRealm
| LuaTokenKind::TkTagReturnCast
| LuaTokenKind::TkTagExport
| LuaTokenKind::TkLanguage
| LuaTokenKind::TkTagAttribute
| LuaTokenKind::TKTagSchema => {
builder.push_with_modifier(
token,
SemanticTokenType::KEYWORD,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkDocKeyOf
| LuaTokenKind::TkDocExtends
| LuaTokenKind::TkDocNew
| LuaTokenKind::TkDocAs
| LuaTokenKind::TkDocIn
| LuaTokenKind::TkDocInfer
| LuaTokenKind::TkDocReadonly => {
builder.push_with_modifier(
token,
SemanticTokenType::KEYWORD,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkNormalStart | LuaTokenKind::TKNonStdComment => {
builder.push(token, SemanticTokenType::COMMENT);
}
LuaTokenKind::TkDocDetail => {
let rendering_description = token
.parent()
.is_some_and(|parent| parent.kind() == LuaSyntaxKind::DocDescription.into());
let description_parsing_is_enabled = emmyrc.semantic_tokens.render_documentation_markup;
if !(rendering_description && description_parsing_is_enabled) {
builder.push(token, SemanticTokenType::COMMENT);
}
}
LuaTokenKind::TkDocQuestion | LuaTokenKind::TkDocOr | LuaTokenKind::TkDocAnd => {
builder.push_with_modifier(
token,
SemanticTokenType::OPERATOR,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkDocVisibility | LuaTokenKind::TkTagVisibility => {
builder.push_with_modifiers(
token,
SemanticTokenType::KEYWORD,
&[
SemanticTokenModifier::MODIFICATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
LuaTokenKind::TkDocVersionNumber => {
builder.push_with_modifier(
token,
SemanticTokenType::NUMBER,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkStringTemplateType => {
builder.push_with_modifier(
token,
SemanticTokenType::STRING,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkDocMatch => {
builder.push_with_modifier(
token,
SemanticTokenType::KEYWORD,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TKDocPath | LuaTokenKind::TkDocSeeContent => {
builder.push_with_modifier(
token,
SemanticTokenType::STRING,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaTokenKind::TkDocRegion | LuaTokenKind::TkDocEndRegion => {
builder.push(token, SemanticTokenType::COMMENT);
}
LuaTokenKind::TkDocStart | LuaTokenKind::TkDocContinue | LuaTokenKind::TkDocContinueOr => {
render_doc_at(builder, token)
}
_ => {}
}
}
fn get_global_rooted_index_depth(
semantic_model: &glua_code_analysis::SemanticModel,
index_expr: &glua_parser::LuaIndexExpr,
) -> Option<usize> {
let mut current = index_expr.clone();
let mut depth = 1;
while let Some(prefix) = current.get_prefix_expr() {
if let glua_parser::LuaExpr::NameExpr(name_expr) = prefix {
if let Some(glua_code_analysis::LuaSemanticDeclId::LuaDecl(id)) = semantic_model
.find_decl(
name_expr.syntax().clone().into(),
glua_code_analysis::SemanticDeclLevel::NoTrace,
)
{
if semantic_model
.get_db()
.get_decl_index()
.get_decl(&id)
.is_some_and(|d| d.is_global())
{
return Some(depth);
}
}
return None;
} else if let glua_parser::LuaExpr::IndexExpr(expr) = prefix {
current = expr;
depth += 1;
} else {
return None;
}
}
None
}
fn build_node_semantic_token(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
node: LuaSyntaxNode,
emmyrc: &Emmyrc,
) -> Option<()> {
match LuaAst::cast(node)? {
LuaAst::LuaDocTagClass(doc_class) => {
if let Some(name) = doc_class.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::CLASS,
SemanticTokenModifier::DECLARATION,
);
}
if let Some(attribs) = doc_class.get_type_flag() {
for token in attribs.tokens::<LuaGeneralToken>() {
builder.push(token.syntax(), SemanticTokenType::DECORATOR);
}
}
if let Some(generic_list) = doc_class.get_generic_decl() {
render_type_parameter_list(builder, &generic_list);
}
}
LuaAst::LuaDocTagEnum(doc_enum) => {
let name = doc_enum.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::ENUM,
SemanticTokenModifier::DECLARATION,
);
if let Some(attribs) = doc_enum.get_type_flag() {
for token in attribs.tokens::<LuaGeneralToken>() {
builder.push(token.syntax(), SemanticTokenType::DECORATOR);
}
}
}
LuaAst::LuaDocTagAlias(doc_alias) => {
let name = doc_alias.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::TYPE,
SemanticTokenModifier::DECLARATION,
);
if let Some(generic_decl_list) = doc_alias.get_generic_decl_list() {
render_type_parameter_list(builder, &generic_decl_list);
}
}
LuaAst::LuaDocTagField(doc_field) => {
if let Some(LuaDocFieldKey::Name(name)) = doc_field.get_field_key() {
builder.push_with_modifiers(
name.syntax(),
CustomSemanticTokenType::FIELD,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
}
LuaAst::LuaDocTagDiagnostic(doc_diagnostic) => {
let name = doc_diagnostic.get_action_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::PROPERTY,
SemanticTokenModifier::DOCUMENTATION,
);
if let Some(code_list) = doc_diagnostic.get_code_list() {
for code in code_list.get_codes() {
builder.push_with_modifier(
code.syntax(),
SemanticTokenType::REGEXP,
SemanticTokenModifier::DOCUMENTATION,
);
}
}
}
LuaAst::LuaDocTagParam(doc_param) => {
let name = doc_param.get_name_token()?;
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::PARAMETER,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
LuaAst::LuaDocTagRealm(doc_realm) => {
if let Some(realm) = doc_realm.get_name_token() {
builder.push_with_modifiers(
realm.syntax(),
SemanticTokenType::ENUM_MEMBER,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
}
LuaAst::LuaDocTagFileparam(doc_fileparam) => {
if let Some(name) = doc_fileparam.get_name_token() {
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::PARAMETER,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
}
LuaAst::LuaDocTagReturn(doc_return) => {
let type_name_list = doc_return.get_info_list();
for (_, name) in type_name_list {
if let Some(name) = name {
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::VARIABLE,
SemanticTokenModifier::DOCUMENTATION,
);
}
}
}
LuaAst::LuaDocTagCast(doc_cast) => {
if let Some(target_expr) = doc_cast.get_key_expr() {
match target_expr {
LuaExpr::NameExpr(name_expr) => {
builder.push(
name_expr.get_name_token()?.syntax(),
SemanticTokenType::VARIABLE,
);
}
LuaExpr::IndexExpr(index_expr) => {
let position = index_expr.syntax().text_range().start();
let len = index_expr.syntax().text_range().len();
builder.push_at_position(
position,
len.into(),
SemanticTokenType::VARIABLE,
None,
);
}
_ => {}
}
}
if let Some(NodeOrToken::Token(token)) = doc_cast.syntax().prev_sibling_or_token()
&& token.kind() == LuaKind::Token(LuaTokenKind::TkDocLongStart)
{
render_doc_at(builder, &token);
}
}
LuaAst::LuaDocTagAs(doc_as) => {
if let Some(NodeOrToken::Token(token)) = doc_as.syntax().prev_sibling_or_token()
&& token.kind() == LuaKind::Token(LuaTokenKind::TkDocLongStart)
{
render_doc_at(builder, &token);
}
}
LuaAst::LuaDocTagGeneric(doc_generic) => {
let type_parameter_list = doc_generic.get_generic_decl_list()?;
render_type_parameter_list(builder, &type_parameter_list);
}
LuaAst::LuaDocTagNamespace(doc_namespace) => {
let name = doc_namespace.get_name_token()?;
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::NAMESPACE,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
LuaAst::LuaDocTagUsing(doc_using) => {
let name = doc_using.get_name_token()?;
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::NAMESPACE,
SemanticTokenModifier::DOCUMENTATION,
);
}
LuaAst::LuaDocTagExport(doc_export) => {
let name = doc_export.get_name_token()?;
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::NAMESPACE,
&[
SemanticTokenModifier::MODIFICATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
LuaAst::LuaParamName(param_name) => {
let name_token = param_name.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(
semantic_model,
builder,
param_name.syntax(),
&name_token,
false,
);
}
LuaAst::LuaLocalName(local_name) => {
let name_token = local_name.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(
semantic_model,
builder,
local_name.syntax(),
&name_token,
false,
)
.or_else(|| {
builder.push_with_modifiers(
name_token.syntax(),
SemanticTokenType::VARIABLE,
&[
SemanticTokenModifier::DECLARATION,
CustomSemanticTokenModifier::LOCAL,
],
)
});
}
LuaAst::LuaNameExpr(name_expr) => {
let name_token = name_expr.get_name_token()?;
if builder.contains_token(name_token.syntax()) {
return Some(());
}
handle_name_node(
semantic_model,
builder,
name_expr.syntax(),
&name_token,
true,
)
.unwrap_or_else(|| {
let name_text = name_token.get_name_text();
builder.push(
name_token.syntax(),
default_identifier_token_type(name_text),
);
});
}
LuaAst::LuaForRangeStat(for_range_stat) => {
for name in for_range_stat.get_var_name_list() {
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::VARIABLE,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::READONLY,
CustomSemanticTokenModifier::LOCAL,
],
);
}
}
LuaAst::LuaForStat(for_stat) => {
let name = for_stat.get_var_name()?;
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::VARIABLE,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::READONLY,
CustomSemanticTokenModifier::LOCAL,
],
);
}
LuaAst::LuaLocalFuncStat(local_func_stat) => {
let name = local_func_stat.get_local_name()?.get_name_token()?;
let mut modifiers = vec![SemanticTokenModifier::DECLARATION];
if let Some(semantic_decl) = semantic_model.find_decl(
local_func_stat.get_local_name()?.syntax().clone().into(),
glua_code_analysis::SemanticDeclLevel::NoTrace,
) {
let decl_type = match semantic_decl {
glua_code_analysis::LuaSemanticDeclId::Member(member_id) => {
semantic_model.get_type(member_id.into())
}
glua_code_analysis::LuaSemanticDeclId::LuaDecl(decl_id) => {
semantic_model.get_type(decl_id.into())
}
_ => glua_code_analysis::LuaType::Unknown,
};
enrich_modifiers_from_decl(
semantic_model,
&semantic_decl,
&decl_type,
&mut modifiers,
);
}
builder.push_with_modifiers(name.syntax(), SemanticTokenType::FUNCTION, &modifiers);
}
LuaAst::LuaFuncStat(func_stat) => {
let func_name = func_stat.get_func_name()?;
match func_name {
LuaVarExpr::NameExpr(name_expr) => {
let name = name_expr.get_name_token()?;
let mut modifiers = vec![SemanticTokenModifier::DECLARATION];
if let Some(semantic_decl) = semantic_model.find_decl(
name_expr.syntax().clone().into(),
glua_code_analysis::SemanticDeclLevel::NoTrace,
) {
let decl_type = match semantic_decl {
glua_code_analysis::LuaSemanticDeclId::Member(member_id) => {
semantic_model.get_type(member_id.into())
}
glua_code_analysis::LuaSemanticDeclId::LuaDecl(decl_id) => {
semantic_model.get_type(decl_id.into())
}
_ => glua_code_analysis::LuaType::Unknown,
};
enrich_modifiers_from_decl(
semantic_model,
&semantic_decl,
&decl_type,
&mut modifiers,
);
}
builder.push_with_modifiers(
name.syntax(),
SemanticTokenType::FUNCTION,
&modifiers,
);
}
LuaVarExpr::IndexExpr(index_expr) => {
let name = index_expr.get_index_name_token()?;
let mut modifiers = vec![SemanticTokenModifier::DECLARATION];
if let Some(semantic_decl) = semantic_model.find_decl(
index_expr.syntax().clone().into(),
glua_code_analysis::SemanticDeclLevel::NoTrace,
) {
let decl_type = match semantic_decl {
glua_code_analysis::LuaSemanticDeclId::Member(member_id) => {
semantic_model.get_type(member_id.into())
}
glua_code_analysis::LuaSemanticDeclId::LuaDecl(decl_id) => {
semantic_model.get_type(decl_id.into())
}
_ => glua_code_analysis::LuaType::Unknown,
};
enrich_modifiers_from_decl(
semantic_model,
&semantic_decl,
&decl_type,
&mut modifiers,
);
}
builder.push_with_modifiers(&name, SemanticTokenType::METHOD, &modifiers);
}
}
}
LuaAst::LuaLocalAttribute(local_attribute) => {
let name = local_attribute.get_name_token()?;
builder.push(name.syntax(), SemanticTokenType::KEYWORD);
}
LuaAst::LuaLabelStat(label_stat) => {
let name = label_stat.get_label_name_token()?;
builder.push_with_modifier_force(
name.syntax(),
CustomSemanticTokenType::LABEL,
SemanticTokenModifier::DECLARATION,
);
}
LuaAst::LuaGotoStat(goto_stat) => {
let name = goto_stat.get_label_name_token()?;
builder.push_force(name.syntax(), CustomSemanticTokenType::LABEL);
}
LuaAst::LuaCallExpr(call_expr) => {
let prefix = call_expr.get_prefix_expr()?;
match prefix {
LuaExpr::NameExpr(ref name_expr) => {
let name = name_expr.get_name_token()?;
if builder.contains_token(name.syntax()) {
return Some(());
}
let name_text = name.get_name_text();
let prefix_type = semantic_model.infer_expr(prefix).ok();
render_callable_name_token(
semantic_model,
builder,
name.syntax(),
name_text,
prefix_type,
)?;
}
LuaExpr::IndexExpr(ref index_expr) => {
let name = index_expr.get_name_token()?;
if builder.contains_token(name.syntax()) {
return Some(());
}
}
_ => {}
}
}
LuaAst::LuaDocNameType(doc_name_type) => {
let name = doc_name_type.get_name_token()?;
let name_text = name.get_name_text();
if name_text == "self"
|| name_text == "nil"
|| name_text == "boolean"
|| name_text == "number"
|| name_text == "string"
|| name_text == "table"
|| name_text == "function"
|| name_text == "userdata"
|| name_text == "thread"
{
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::TYPE,
SemanticTokenModifier::DEFAULT_LIBRARY,
);
} else {
builder.push(name.syntax(), SemanticTokenType::TYPE);
}
}
LuaAst::LuaDocObjectType(doc_object_type) => {
let fields = doc_object_type.get_fields();
for field in fields {
if let Some(field_key) = field.get_field_key()
&& let LuaDocObjectFieldKey::Name(name) = &field_key
{
builder.push_with_modifiers(
name.syntax(),
CustomSemanticTokenType::FIELD,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DOCUMENTATION,
],
);
}
}
}
LuaAst::LuaDocFuncType(doc_func_type) => {
for name_token in doc_func_type.tokens::<LuaNameToken>() {
match name_token.get_name_text() {
"fun" => {
builder.push(name_token.syntax(), SemanticTokenType::KEYWORD);
}
"async" => {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenType::KEYWORD,
SemanticTokenModifier::ASYNC,
);
}
_ => {}
}
}
for param in doc_func_type.get_params() {
let name = param.get_name_token()?;
builder.push(name.syntax(), SemanticTokenType::PARAMETER);
}
}
LuaAst::LuaIndexExpr(index_expr) => {
if let Some(LuaExpr::NameExpr(prefix_name_expr)) = index_expr.get_prefix_expr()
&& let Some(prefix_name) = prefix_name_expr.get_name_token()
&& !builder.contains_token(prefix_name.syntax())
&& let Some(LuaSemanticDeclId::LuaDecl(prefix_decl_id)) = semantic_model.find_decl(
prefix_name_expr.syntax().clone().into(),
SemanticDeclLevel::NoTrace,
)
&& let Some(prefix_decl) = semantic_model
.get_db()
.get_decl_index()
.get_decl(&prefix_decl_id)
&& is_require_decl(semantic_model, prefix_decl)
{
builder.push(prefix_name.syntax(), SemanticTokenType::NAMESPACE);
}
let name = index_expr.get_name_token()?;
if builder.contains_token(name.syntax()) {
return Some(());
}
let semantic_decl = semantic_model
.find_decl(name.syntax().clone().into(), SemanticDeclLevel::default());
if let Some(property_owner) = semantic_decl
&& let LuaSemanticDeclId::Member(member_id) = property_owner
{
let decl_type = semantic_model.get_type(member_id.into());
if is_function_like_type(&decl_type) {
let mut modifiers = vec![];
let mut token_type = CustomSemanticTokenType::FIELD;
if let Some(member) = semantic_model
.get_db()
.get_member_index()
.get_member(&member_id)
{
if is_method_like_member(semantic_model, member)
|| is_default_library_type(semantic_model, &decl_type)
|| index_prefix_is_namespace_like(semantic_model, &index_expr)
{
token_type = SemanticTokenType::METHOD;
}
}
modifiers.push(CustomSemanticTokenModifier::CALLABLE);
enrich_modifiers_from_decl(
semantic_model,
&property_owner,
&decl_type,
&mut modifiers,
);
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
token_type,
&modifiers,
);
return Some(());
}
if decl_type.is_def() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::CLASS,
SemanticTokenModifier::READONLY,
);
return Some(());
}
let global_depth = get_global_rooted_index_depth(semantic_model, &index_expr);
let owner_id = semantic_model
.get_db()
.get_member_index()
.get_current_owner(&member_id);
if let Some(glua_code_analysis::LuaMemberOwner::Type(type_id)) = owner_id
&& let Some(type_decl) = semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)
&& type_decl.is_enum()
{
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::ENUM_MEMBER,
SemanticTokenModifier::READONLY,
);
return Some(());
}
let is_class_like = is_table_like_type(&decl_type);
let mut is_namespace_like = false;
if !is_class_like && global_depth == Some(1) {
if let Some(member) = semantic_model
.get_db()
.get_member_index()
.get_member(&member_id)
{
if let Some(global_id) = member.get_global_id() {
is_namespace_like = global_path_has_class_like_children(
semantic_model,
builder,
global_id,
);
}
}
}
if is_class_like && global_depth == Some(1) {
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
SemanticTokenType::CLASS,
&[],
);
return Some(());
}
if is_namespace_like && global_depth == Some(1) {
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
SemanticTokenType::NAMESPACE,
&[],
);
return Some(());
}
if is_function_like_type(&decl_type) {
let modifiers = vec![CustomSemanticTokenModifier::CALLABLE];
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
CustomSemanticTokenType::FIELD,
&modifiers,
);
} else {
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
CustomSemanticTokenType::FIELD,
&[],
);
}
return Some(());
}
if index_expr
.syntax()
.parent()
.is_some_and(|p| p.kind() == LuaSyntaxKind::CallExpr.into())
{
let namespace_like_prefix =
index_prefix_is_namespace_like(semantic_model, &index_expr);
let modifiers = vec![CustomSemanticTokenModifier::CALLABLE];
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
if namespace_like_prefix {
SemanticTokenType::METHOD
} else {
CustomSemanticTokenType::FIELD
},
&modifiers,
);
} else {
push_name_or_syntax_with_context_modifiers(
builder,
name.syntax(),
index_expr.syntax(),
CustomSemanticTokenType::FIELD,
&[],
);
}
}
LuaAst::LuaTableField(table_field) => {
let owner_id =
LuaMemberId::new(table_field.get_syntax_id(), semantic_model.get_file_id());
if let Some(member) = semantic_model
.get_db()
.get_member_index()
.get_member(&owner_id)
{
let owner_id = semantic_model
.get_db()
.get_member_index()
.get_current_owner(&member.get_id());
if let Some(LuaMemberOwner::Type(type_id)) = owner_id
&& let Some(type_decl) = semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)
&& type_decl.is_enum()
{
if let Some(field_name) = table_field.get_field_key()?.get_name() {
builder.push_with_modifier(
field_name.syntax(),
SemanticTokenType::ENUM_MEMBER,
SemanticTokenModifier::DECLARATION,
);
}
return Some(());
}
}
let value_type = semantic_model
.infer_expr(table_field.get_value_expr()?.clone())
.ok()?;
match value_type {
LuaType::Signature(_) | LuaType::DocFunction(_) => {
if let Some(field_name) = table_field.get_field_key()?.get_name() {
let mut modifiers = vec![
SemanticTokenModifier::DECLARATION,
CustomSemanticTokenModifier::CALLABLE,
];
if let Some(member) = semantic_model
.get_db()
.get_member_index()
.get_member(&owner_id)
{
enrich_modifiers_from_decl(
semantic_model,
&glua_code_analysis::LuaSemanticDeclId::Member(member.get_id()),
&value_type,
&mut modifiers,
);
}
builder.push_with_modifiers(
field_name.syntax(),
CustomSemanticTokenType::FIELD,
&modifiers,
);
}
}
LuaType::Union(union) if union.into_vec().iter().any(is_function_like_type) => {
if let Some(field_name) = table_field.get_field_key()?.get_name() {
let modifiers = [
SemanticTokenModifier::DECLARATION,
CustomSemanticTokenModifier::CALLABLE,
];
builder.push_with_modifiers(
field_name.syntax(),
CustomSemanticTokenType::FIELD,
&modifiers,
);
}
}
_ => {
if let Some(field_name) = table_field.get_field_key()?.get_name() {
builder.push_with_modifiers(
field_name.syntax(),
CustomSemanticTokenType::FIELD,
&[SemanticTokenModifier::DECLARATION],
);
}
}
}
}
LuaAst::LuaDocLiteralType(literal) => {
if let LuaLiteralToken::Bool(bool_token) = &literal.get_literal()? {
builder.push_with_modifier(
bool_token.syntax(),
SemanticTokenType::KEYWORD,
SemanticTokenModifier::DOCUMENTATION,
);
}
}
LuaAst::LuaDocDescription(description) => {
if let Some(parent) = description.syntax().parent() {
if let Some(doc_other) = LuaDocTagOther::cast(parent.clone()) {
if doc_other.get_tag_name().as_deref() == Some("hook") {
for token in description.tokens::<LuaGeneralToken>() {
builder.push(token.syntax(), SemanticTokenType::FUNCTION);
}
return Some(());
}
}
}
if !emmyrc.semantic_tokens.render_documentation_markup {
for token in description.tokens::<LuaGeneralToken>() {
if matches!(
token.get_token_kind(),
LuaTokenKind::TkDocDetail | LuaTokenKind::TkNormalStart
) {
builder.push(token.syntax(), SemanticTokenType::COMMENT);
}
}
return None;
}
if let Some(start_token) = description.tokens::<LuaGeneralToken>().next() {
if start_token.get_text().starts_with('#') {
builder.push_at_position(
start_token.get_range().start(),
1,
SemanticTokenType::COMMENT,
None,
);
}
}
let desc_range = description.get_range();
let document = semantic_model.get_document();
let text = document.get_text();
let items = parse_desc(
semantic_model
.get_module()
.map(|m| m.workspace_id)
.unwrap_or(WorkspaceId::MAIN),
emmyrc,
text,
description,
None,
);
render_desc_ranges(builder, text, items, desc_range);
}
LuaAst::LuaDocTagLanguage(language) => {
let name = language.get_name_token()?;
builder.push(name.syntax(), SemanticTokenType::STRING);
let language_text = name.get_name_text();
let comment = language.ancestors::<LuaComment>().next()?;
inject_language(builder, language_text, comment);
}
LuaAst::LuaLiteralExpr(literal_expr) => {
let call_expr = literal_expr
.get_parent::<LuaCallArgList>()?
.get_parent::<LuaCallExpr>()?;
let literal_token = literal_expr.get_literal()?;
if let LuaLiteralToken::String(string_token) = literal_token
&& !builder.is_special_string_range(&string_token.get_range())
{
fun_string_highlight(builder, semantic_model, call_expr, &string_token);
}
}
LuaAst::LuaDocTagAttributeUse(tag_use) => {
if let Some(token) = tag_use.token_by_kind(LuaTokenKind::TkDocAttributeUse) {
builder.push(token.syntax(), SemanticTokenType::KEYWORD);
}
if let Some(token) = tag_use.syntax().last_token() {
builder.push(&token, SemanticTokenType::KEYWORD);
}
for attribute_use in tag_use.get_attribute_uses() {
if let Some(token) = attribute_use.get_type()?.get_name_token() {
builder.push_with_modifiers(
token.syntax(),
SemanticTokenType::DECORATOR,
&[
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DEFAULT_LIBRARY,
],
);
}
}
}
LuaAst::LuaDocTagAttribute(tag_attribute) => {
if let Some(name) = tag_attribute.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::TYPE,
SemanticTokenModifier::DECLARATION,
);
}
if let Some(LuaDocType::Attribute(attribute)) = tag_attribute.get_type() {
for param in attribute.get_params() {
if let Some(name) = param.get_name_token() {
builder.push(name.syntax(), SemanticTokenType::PARAMETER);
}
}
}
}
LuaAst::LuaDocInferType(infer_type) => {
if let Some(gen_decl) = infer_type.get_generic_decl() {
render_type_parameter(builder, &gen_decl);
}
if let Some(name) = infer_type.token::<LuaNameToken>() {
if name.get_name_text() == "infer" {
builder.push(name.syntax(), SemanticTokenType::COMMENT);
}
}
}
_ => {}
}
Some(())
}
fn handle_name_node(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
node: &LuaSyntaxNode,
name_token: &LuaNameToken,
allow_scoped_class_fallback: bool,
) -> Option<()> {
let name_text = name_token.get_name_text();
if name_text == "self" {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenType::VARIABLE,
SemanticTokenModifier::DEFINITION,
);
return Some(());
}
let semantic_decl = semantic_model.find_decl(node.clone().into(), SemanticDeclLevel::NoTrace);
if allow_scoped_class_fallback
&& is_scoped_scripted_class_name(semantic_model, name_text)
&& !semantic_decl_is_local_decl(semantic_model, semantic_decl.as_ref())
&& !has_local_decl_in_scope(semantic_model, name_text, name_token)
{
builder.push(name_token.syntax(), SemanticTokenType::CLASS);
return Some(());
}
let semantic_decl = semantic_decl?;
match semantic_decl {
LuaSemanticDeclId::Member(member_id) => {
let decl_type = semantic_model.get_type(member_id.into());
let mut modifiers = vec![];
enrich_modifiers_from_decl(semantic_model, &semantic_decl, &decl_type, &mut modifiers);
if matches!(decl_type, LuaType::Signature(_)) {
push_name_or_syntax_with_context_modifiers(
builder,
name_token.syntax(),
node,
SemanticTokenType::FUNCTION,
&modifiers,
);
return Some(());
}
}
LuaSemanticDeclId::LuaDecl(decl_id) => {
let decl = semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)?;
let decl_type = semantic_model.get_type(decl_id.into());
let decl_is_default_library = is_default_library_decl(semantic_model, decl);
let is_declaration = decl.get_range() == name_token.syntax().text_range();
let is_scoped_class_global = is_scoped_scripted_class_global(semantic_model, decl);
if decl.is_global() && decl_is_default_library {
if matches!(decl_type, LuaType::Namespace(_)) {
builder.push_with_modifier(
name_token.syntax(),
SemanticTokenType::NAMESPACE,
SemanticTokenModifier::DEFAULT_LIBRARY,
);
return Some(());
}
}
let base_type = if is_scoped_class_global {
SemanticTokenType::CLASS
} else if decl.is_param() {
SemanticTokenType::PARAMETER
} else {
SemanticTokenType::VARIABLE
};
let param_callable = callable_parameter_type(semantic_model, decl);
let decl_is_callable = is_function_like_type(&decl_type);
if is_require_decl(semantic_model, decl) {
let mut modifiers = vec![SemanticTokenModifier::READONLY];
if is_declaration {
modifiers.push(SemanticTokenModifier::DECLARATION);
}
if decl.is_local() && !decl.is_param() {
modifiers.push(CustomSemanticTokenModifier::LOCAL);
}
builder.push_with_modifiers(
name_token.syntax(),
SemanticTokenType::NAMESPACE,
&modifiers,
);
return Some(());
}
let alias_token_type = local_alias_target_token_type(semantic_model, builder, decl);
let mut modifiers = vec![];
enrich_modifiers_from_decl(semantic_model, &semantic_decl, &decl_type, &mut modifiers);
let (token_type, mut modifier) = if let Some(alias_token_type) = alias_token_type {
alias_token_type
} else {
match &decl_type {
LuaType::Def(type_id) => (
semantic_token_type_for_type_decl(semantic_model, type_id)
.unwrap_or(SemanticTokenType::CLASS),
None,
),
LuaType::Ref(ref_id) => {
if check_ref_is_require_def(semantic_model, decl, ref_id).unwrap_or(false) {
(
SemanticTokenType::CLASS,
Some(SemanticTokenModifier::READONLY),
)
} else {
let token_type = if decl.is_global() || is_scoped_class_global {
semantic_token_type_for_type_decl(semantic_model, ref_id)
.unwrap_or(base_type)
} else {
base_type
};
(token_type, None)
}
}
LuaType::Namespace(_) => (
SemanticTokenType::NAMESPACE,
decl_is_default_library.then_some(SemanticTokenModifier::DEFAULT_LIBRARY),
),
LuaType::Signature(signature) => {
let is_meta = semantic_model
.get_db()
.get_module_index()
.is_meta_file(&signature.get_file_id());
let token_type = if decl.is_param() || decl.get_value_syntax_id().is_some()
{
base_type
} else {
SemanticTokenType::FUNCTION
};
(
token_type,
is_meta.then_some(SemanticTokenModifier::DEFAULT_LIBRARY),
)
}
LuaType::DocFunction(_) | LuaType::Union(_) => (base_type, None),
_ => (base_type, None),
}
};
if modifier.is_none() && is_decl_readonly(decl) {
modifier = Some(SemanticTokenModifier::READONLY);
}
if is_declaration {
modifiers.push(SemanticTokenModifier::DECLARATION);
}
if is_modification_target(node) {
modifiers.push(SemanticTokenModifier::MODIFICATION);
}
if param_callable || decl_is_callable {
modifiers.push(CustomSemanticTokenModifier::CALLABLE);
}
if token_type == SemanticTokenType::VARIABLE
|| token_type == SemanticTokenType::PARAMETER
{
if decl.is_global()
&& is_index_expr_prefix(node)
&& global_path_has_class_like_children(
semantic_model,
builder,
&GlobalId::new(decl.get_name()),
)
{
builder.push(name_token.syntax(), SemanticTokenType::NAMESPACE);
return Some(());
}
if decl.is_global() {
modifiers.push(CustomSemanticTokenModifier::GLOBAL);
} else if !decl.is_param() && !is_scoped_class_global {
modifiers.push(CustomSemanticTokenModifier::LOCAL);
}
if is_object_like_decl_type(semantic_model, decl, &decl_type) {
modifiers.push(CustomSemanticTokenModifier::OBJECT);
}
}
if token_type != SemanticTokenType::VARIABLE
&& token_type != SemanticTokenType::PARAMETER
&& decl.is_local()
&& !decl.is_param()
&& decl.get_value_syntax_id().is_some()
{
modifiers.push(CustomSemanticTokenModifier::LOCAL);
}
if let Some(modifier) = modifier {
modifiers.push(modifier);
}
if !modifiers.is_empty() {
builder.push_with_modifiers(name_token.syntax(), token_type, &modifiers);
} else {
builder.push(name_token.syntax(), token_type);
}
return Some(());
}
_ => {}
}
builder.push(
name_token.syntax(),
default_identifier_token_type(name_text),
);
Some(())
}
fn default_identifier_token_type(name_text: &str) -> SemanticTokenType {
let _ = name_text;
SemanticTokenType::VARIABLE
}
fn render_callable_name_token(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
token: &LuaSyntaxToken,
name_text: &str,
value_type: Option<LuaType>,
) -> Option<()> {
let _ = name_text;
match value_type {
Some(LuaType::Signature(signature)) => {
let is_meta = semantic_model
.get_db()
.get_module_index()
.is_meta_file(&signature.get_file_id());
if is_meta {
builder.push_with_modifiers(
token,
SemanticTokenType::FUNCTION,
&[
SemanticTokenModifier::DEFAULT_LIBRARY,
SemanticTokenModifier::READONLY,
],
)
} else {
builder.push_with_modifier(
token,
SemanticTokenType::FUNCTION,
CustomSemanticTokenModifier::CALLABLE,
)
}
}
Some(LuaType::DocFunction(_)) => builder.push_with_modifier(
token,
SemanticTokenType::FUNCTION,
CustomSemanticTokenModifier::CALLABLE,
),
Some(LuaType::Union(union)) if union.into_vec().iter().any(|typ| typ.is_function()) => {
builder.push_with_modifier(
token,
SemanticTokenType::FUNCTION,
CustomSemanticTokenModifier::CALLABLE,
)
}
Some(other) if other.is_function() => builder.push_with_modifier(
token,
SemanticTokenType::FUNCTION,
CustomSemanticTokenModifier::CALLABLE,
),
_ => builder.push_with_modifier(
token,
SemanticTokenType::FUNCTION,
CustomSemanticTokenModifier::CALLABLE,
),
}
}
fn callable_parameter_type(semantic_model: &SemanticModel, decl: &LuaDecl) -> bool {
match &decl.extra {
LuaDeclExtra::Param {
idx, signature_id, ..
} => semantic_model
.get_db()
.get_signature_index()
.get(signature_id)
.and_then(|signature| signature.get_param_info_by_id(*idx))
.is_some_and(|param_info| is_function_like_type(¶m_info.type_ref)),
_ => false,
}
}
fn is_default_library_decl(semantic_model: &SemanticModel, decl: &LuaDecl) -> bool {
let module_index = semantic_model.get_db().get_module_index();
let file_id = decl.get_file_id();
module_index.is_std(&file_id)
|| module_index.is_library(&file_id)
|| module_index.is_meta_file(&file_id)
}
fn semantic_token_type_for_type_decl(
semantic_model: &SemanticModel,
type_id: &LuaTypeDeclId,
) -> Option<SemanticTokenType> {
let type_decl = semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)?;
if type_decl.is_enum() {
Some(SemanticTokenType::ENUM)
} else if type_decl.is_class() {
Some(SemanticTokenType::CLASS)
} else if type_decl.is_alias() {
Some(SemanticTokenType::TYPE)
} else {
None
}
}
fn is_object_like_decl_type(
semantic_model: &SemanticModel,
decl: &LuaDecl,
decl_type: &LuaType,
) -> bool {
if decl.is_global() {
return false;
}
is_object_like_value_type(semantic_model, decl_type)
}
fn is_object_like_value_type(semantic_model: &SemanticModel, decl_type: &LuaType) -> bool {
match decl_type {
LuaType::Ref(type_id) => semantic_model
.get_db()
.get_type_index()
.get_type_decl(type_id)
.is_some_and(|type_decl| type_decl.is_class()),
LuaType::Instance(_)
| LuaType::Object(_)
| LuaType::Table
| LuaType::TableConst(_)
| LuaType::TableGeneric(_)
| LuaType::TableOf(_) => true,
LuaType::Union(union) => union
.into_vec()
.iter()
.any(|typ| is_object_like_value_type(semantic_model, typ)),
_ => false,
}
}
fn is_scoped_scripted_class_global(semantic_model: &SemanticModel, decl: &LuaDecl) -> bool {
decl.is_global() && is_scoped_scripted_class_name(semantic_model, decl.get_name())
}
fn semantic_decl_is_local_decl(
semantic_model: &SemanticModel,
semantic_decl: Option<&LuaSemanticDeclId>,
) -> bool {
let Some(LuaSemanticDeclId::LuaDecl(decl_id)) = semantic_decl else {
return false;
};
semantic_model
.get_db()
.get_decl_index()
.get_decl(decl_id)
.is_some_and(|decl| decl.is_local())
}
fn has_local_decl_in_scope(
semantic_model: &SemanticModel,
name: &str,
name_token: &LuaNameToken,
) -> bool {
semantic_model
.get_db()
.get_decl_index()
.get_decl_tree(&semantic_model.get_file_id())
.and_then(|tree| tree.find_local_decl(name, name_token.syntax().text_range().start()))
.is_some()
}
fn is_scoped_scripted_class_name(semantic_model: &SemanticModel, name: &str) -> bool {
if !name.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
return false;
}
let db = semantic_model.get_db();
if !db.get_emmyrc().gmod.enabled {
return false;
}
let Some(file_path) = db.get_vfs().get_file_path(&semantic_model.get_file_id()) else {
return false;
};
db.get_emmyrc()
.gmod
.scripted_class_scopes
.detect_class_for_path(file_path)
.is_some_and(|scope_match| scope_match.definition.class_global == name)
}
fn is_function_like_type(decl_type: &LuaType) -> bool {
match decl_type {
LuaType::DocFunction(_) => true,
LuaType::Union(union) => union.into_vec().iter().any(|typ| typ.is_function()),
_ => decl_type.is_function(),
}
}
fn is_method_like_member(
semantic_model: &SemanticModel,
member: &glua_code_analysis::LuaMember,
) -> bool {
let module_index = semantic_model.get_db().get_module_index();
let file_id = member.get_file_id();
if module_index.is_std(&file_id)
|| module_index.is_library(&file_id)
|| module_index.is_meta_file(&file_id)
{
return true;
}
matches!(
member.get_feature(),
LuaMemberFeature::FileMethodDecl
| LuaMemberFeature::MetaMethodDecl
| LuaMemberFeature::MetaDefine
)
}
fn is_default_library_type(semantic_model: &SemanticModel, decl_type: &LuaType) -> bool {
let module_index = semantic_model.get_db().get_module_index();
match decl_type {
LuaType::Signature(signature_id) => {
let file_id = signature_id.get_file_id();
module_index.is_std(&file_id)
|| module_index.is_library(&file_id)
|| module_index.is_meta_file(&file_id)
}
LuaType::Union(union) => union
.into_vec()
.iter()
.any(|typ| is_default_library_type(semantic_model, typ)),
_ => false,
}
}
fn index_prefix_is_namespace_like(
semantic_model: &SemanticModel,
index_expr: &glua_parser::LuaIndexExpr,
) -> bool {
let Some(prefix_expr) = index_expr.get_prefix_expr() else {
return false;
};
if semantic_model
.infer_expr(prefix_expr.clone())
.ok()
.is_some_and(|typ| matches!(typ, LuaType::Namespace(_)))
{
return true;
}
let LuaExpr::NameExpr(prefix_name_expr) = prefix_expr else {
return false;
};
let Some(LuaSemanticDeclId::LuaDecl(decl_id)) = semantic_model.find_decl(
prefix_name_expr.syntax().clone().into(),
SemanticDeclLevel::NoTrace,
) else {
return false;
};
let Some(decl) = semantic_model.get_db().get_decl_index().get_decl(&decl_id) else {
return false;
};
let decl_type = semantic_model.get_type(decl_id.into());
is_default_library_decl(semantic_model, decl) && matches!(decl_type, LuaType::Namespace(_))
}
fn push_name_or_syntax_with_context_modifiers(
builder: &mut SemanticBuilder,
token: &LuaSyntaxToken,
syntax: &LuaSyntaxNode,
token_type: SemanticTokenType,
modifiers: &[SemanticTokenModifier],
) -> Option<()> {
let mut contextual_modifiers = modifiers.to_vec();
if is_modification_target(syntax) {
contextual_modifiers.push(SemanticTokenModifier::MODIFICATION);
}
if contextual_modifiers.is_empty() {
builder.push(token, token_type)
} else {
builder.push_with_modifiers(token, token_type, &contextual_modifiers)
}
}
fn is_modification_target(node: &LuaSyntaxNode) -> bool {
let Some(name_expr) = LuaExpr::cast(node.clone()) else {
return false;
};
let Some(assign_stat) = node.ancestors().find_map(LuaAssignStat::cast) else {
return false;
};
let (vars, _) = assign_stat.get_var_and_expr_list();
vars.into_iter()
.any(|var| var.syntax() == name_expr.syntax())
}
fn is_decl_readonly(decl: &LuaDecl) -> bool {
matches!(
&decl.extra,
LuaDeclExtra::Local {
attrib: Some(LocalAttribute::Const | LocalAttribute::IterConst),
..
} | LuaDeclExtra::ImplicitSelf { .. }
)
}
fn is_index_expr_prefix(node: &glua_parser::LuaSyntaxNode) -> bool {
node.parent()
.is_some_and(|p| p.kind() == glua_parser::LuaSyntaxKind::IndexExpr.into())
}
fn is_table_like_type(decl_type: &glua_code_analysis::LuaType) -> bool {
matches!(
decl_type,
glua_code_analysis::LuaType::Table
| glua_code_analysis::LuaType::TableConst(_)
| glua_code_analysis::LuaType::Object(_)
)
}
fn global_path_has_class_like_children(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
global_id: &GlobalId,
) -> bool {
if let Some(is_class_like) = builder.cached_class_like_global(global_id) {
return is_class_like;
}
let db = semantic_model.get_db();
let is_class_like =
owner_has_multiple_callable_children(db, &LuaMemberOwner::GlobalPath(global_id.clone()))
|| owner_has_multiple_callable_children(
db,
&LuaMemberOwner::Type(LuaTypeDeclId::global(global_id.get_name())),
);
builder.cache_class_like_global(global_id.clone(), is_class_like);
is_class_like
}
fn owner_has_multiple_callable_children(db: &DbIndex, owner: &LuaMemberOwner) -> bool {
let Some(members) = db.get_member_index().get_members(owner) else {
return false;
};
members.iter().any(|child| member_is_callable(db, child))
}
fn member_is_callable(db: &DbIndex, child: &glua_code_analysis::LuaMember) -> bool {
matches!(
child.get_feature(),
LuaMemberFeature::FileMethodDecl
| LuaMemberFeature::MetaMethodDecl
| LuaMemberFeature::MetaDefine
) || db
.get_type_index()
.get_type_cache(&LuaTypeOwner::Member(child.get_id()))
.is_some_and(|type_cache| type_cache.is_function())
}
fn render_doc_at(builder: &mut SemanticBuilder, token: &LuaSyntaxToken) {
let text = token.text();
let mut start = 0;
let mut len = 0;
for (i, c) in text.char_indices() {
if matches!(c, '@' | '|') {
start = i;
if c == '|' && text[i + c.len_utf8()..].starts_with(['+', '>']) {
len = 2;
} else {
len = 1;
}
break;
}
}
builder.push_at_range(
&text[..start],
TextRange::at(token.text_range().start(), TextSize::new(start as u32)),
SemanticTokenType::COMMENT,
&[],
);
builder.push_at_range(
&text[start..start + len],
TextRange::at(
token.text_range().start() + TextSize::new(start as u32),
TextSize::new(len as u32),
),
SemanticTokenType::KEYWORD,
&[SemanticTokenModifier::DOCUMENTATION],
);
}
fn render_desc_ranges(
builder: &mut SemanticBuilder,
text: &str,
items: Vec<DescItem>,
desc_range: TextRange,
) {
let mut pos = desc_range.start();
for item in items {
if item.range.start() > pos {
let detail_range = TextRange::new(pos, item.range.start());
builder.push_at_range(
&text[detail_range],
detail_range,
SemanticTokenType::COMMENT,
&[],
);
}
let token_text = &text[item.range];
match item.kind {
DescItemKind::Code | DescItemKind::CodeBlock | DescItemKind::Ref => {
builder.push_at_range(
token_text,
item.range,
SemanticTokenType::VARIABLE,
&[SemanticTokenModifier::DOCUMENTATION],
);
pos = item.range.end();
}
DescItemKind::Link | DescItemKind::JavadocLink => {
builder.push_at_range(
token_text,
item.range,
SemanticTokenType::STRING,
&[SemanticTokenModifier::DOCUMENTATION],
);
pos = item.range.end();
}
DescItemKind::Markup | DescItemKind::Arg => {
builder.push_at_range(
token_text,
item.range,
SemanticTokenType::OPERATOR,
&[SemanticTokenModifier::DOCUMENTATION],
);
pos = item.range.end();
}
DescItemKind::CodeBlockHl(highlight_kind) => {
let token_type = match highlight_kind {
CodeBlockHighlightKind::Keyword => SemanticTokenType::KEYWORD,
CodeBlockHighlightKind::String => SemanticTokenType::STRING,
CodeBlockHighlightKind::Number => SemanticTokenType::NUMBER,
CodeBlockHighlightKind::Comment => SemanticTokenType::COMMENT,
CodeBlockHighlightKind::Function => SemanticTokenType::FUNCTION,
CodeBlockHighlightKind::Class => SemanticTokenType::CLASS,
CodeBlockHighlightKind::Enum => SemanticTokenType::ENUM,
CodeBlockHighlightKind::Variable => SemanticTokenType::VARIABLE,
CodeBlockHighlightKind::Property => SemanticTokenType::PROPERTY,
CodeBlockHighlightKind::Decorator => SemanticTokenType::DECORATOR,
CodeBlockHighlightKind::Operators => SemanticTokenType::OPERATOR,
_ => continue, };
builder.push_at_range(token_text, item.range, token_type, &[]);
pos = item.range.end();
}
_ => {}
}
}
if pos < desc_range.end() {
let detail_range = TextRange::new(pos, desc_range.end());
builder.push_at_range(
&text[detail_range],
detail_range,
SemanticTokenType::COMMENT,
&[],
);
}
}
fn check_ref_is_require_def(
semantic_model: &SemanticModel,
decl: &LuaDecl,
ref_id: &LuaTypeDeclId,
) -> Option<bool> {
let module_info = parse_require_module_info(semantic_model, decl)?;
match &module_info.export_type {
Some(ty) => match ty {
LuaType::Def(id) => Some(id == ref_id),
_ => Some(false),
},
None => None,
}
}
fn local_alias_target_token_type(
semantic_model: &SemanticModel,
builder: &mut SemanticBuilder,
decl: &LuaDecl,
) -> Option<(SemanticTokenType, Option<SemanticTokenModifier>)> {
if !decl.is_local() || decl.is_param() {
return None;
}
let decl_id = decl.get_id();
if let Some(cached) = builder.cached_alias_target(&decl_id) {
return cached;
}
let result = compute_local_alias_target_token_type(semantic_model, decl);
builder.cache_alias_target(decl_id, result.clone());
result
}
fn compute_local_alias_target_token_type(
semantic_model: &SemanticModel,
decl: &LuaDecl,
) -> Option<(SemanticTokenType, Option<SemanticTokenModifier>)> {
let value_syntax_id = decl.get_value_syntax_id()?;
let root = semantic_model
.get_db()
.get_vfs()
.get_syntax_tree(&decl.get_file_id())?
.get_red_root();
let value_node = value_syntax_id.to_node_from_root(&root)?;
let value_expr = LuaExpr::cast(value_node)?;
if let Some(alias_token_type) =
inferred_alias_target_token_type(semantic_model, value_expr.clone())
{
return Some(alias_token_type);
}
if let Some(alias_token_type) = resolved_alias_target_token_type(semantic_model, &value_expr) {
return Some(alias_token_type);
}
lexical_global_alias_target_token_type(semantic_model, &value_expr)
}
fn resolved_alias_target_token_type(
semantic_model: &SemanticModel,
value_expr: &LuaExpr,
) -> Option<(SemanticTokenType, Option<SemanticTokenModifier>)> {
let semantic_decl = semantic_model.find_decl(
value_expr.syntax().clone().into(),
SemanticDeclLevel::NoTrace,
)?;
match semantic_decl {
LuaSemanticDeclId::LuaDecl(target_decl_id) => {
let target_decl = semantic_model
.get_db()
.get_decl_index()
.get_decl(&target_decl_id)?;
let target_type = semantic_model.get_type(target_decl_id.into());
if target_decl.is_global()
&& is_default_library_decl(semantic_model, target_decl)
&& matches!(target_type, LuaType::Namespace(_))
{
return Some((
SemanticTokenType::NAMESPACE,
Some(SemanticTokenModifier::DEFAULT_LIBRARY),
));
}
match target_type {
LuaType::Def(type_id) => {
semantic_token_type_for_type_decl(semantic_model, &type_id)
.map(|token_type| (token_type, None))
}
LuaType::Namespace(_) => Some((SemanticTokenType::NAMESPACE, None)),
_ => None,
}
}
LuaSemanticDeclId::Member(member_id) => {
let target_type = semantic_model.get_type(member_id.into());
match target_type {
LuaType::Def(type_id) => {
semantic_token_type_for_type_decl(semantic_model, &type_id)
.map(|token_type| (token_type, None))
}
LuaType::Namespace(_) => Some((SemanticTokenType::NAMESPACE, None)),
_ => None,
}
}
_ => None,
}
}
fn lexical_global_alias_target_token_type(
semantic_model: &SemanticModel,
value_expr: &LuaExpr,
) -> Option<(SemanticTokenType, Option<SemanticTokenModifier>)> {
if expr_access_path_root_is_local(semantic_model, value_expr) {
return None;
}
let path = expr_access_path(value_expr)?;
let type_id = LuaTypeDeclId::global(&path);
semantic_token_type_for_type_decl(semantic_model, &type_id).map(|token_type| (token_type, None))
}
fn inferred_alias_target_token_type(
semantic_model: &SemanticModel,
value_expr: LuaExpr,
) -> Option<(SemanticTokenType, Option<SemanticTokenModifier>)> {
let value_type = semantic_model.infer_expr(value_expr.clone()).ok()?;
match value_type {
LuaType::Def(type_id) => semantic_token_type_for_type_decl(semantic_model, &type_id)
.map(|token_type| (token_type, None)),
LuaType::Namespace(_) => Some((SemanticTokenType::NAMESPACE, None)),
_ => None,
}
}
fn expr_access_path(value_expr: &LuaExpr) -> Option<String> {
match value_expr {
LuaExpr::NameExpr(name_expr) => name_expr.get_access_path(),
LuaExpr::IndexExpr(index_expr) => index_expr.get_access_path(),
_ => None,
}
}
fn expr_access_path_root_is_local(semantic_model: &SemanticModel, value_expr: &LuaExpr) -> bool {
let root_name_expr = match value_expr {
LuaExpr::NameExpr(name_expr) => Some(name_expr.clone()),
LuaExpr::IndexExpr(index_expr) => {
let mut prefix = index_expr.get_prefix_expr();
while let Some(LuaExpr::IndexExpr(next_index)) = prefix {
prefix = next_index.get_prefix_expr();
}
match prefix {
Some(LuaExpr::NameExpr(name_expr)) => Some(name_expr),
_ => None,
}
}
_ => None,
};
root_name_expr
.and_then(|name_expr| {
semantic_model.find_decl(
name_expr.syntax().clone().into(),
SemanticDeclLevel::NoTrace,
)
})
.and_then(|semantic_decl| match semantic_decl {
LuaSemanticDeclId::LuaDecl(decl_id) => semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)
.map(|decl| decl.is_local()),
_ => Some(false),
})
.unwrap_or(false)
}
fn is_require_decl(semantic_model: &SemanticModel, decl: &LuaDecl) -> bool {
parse_require_module_info(semantic_model, decl).is_some()
}
fn render_type_parameter_list(
builder: &mut SemanticBuilder,
type_parameter_list: &LuaDocGenericDeclList,
) {
for type_decl in type_parameter_list.get_generic_decl() {
render_type_parameter(builder, &type_decl);
}
}
fn render_type_parameter(builder: &mut SemanticBuilder, type_decl: &LuaDocGenericDecl) {
if let Some(name) = type_decl.get_name_token() {
builder.push_with_modifier(
name.syntax(),
SemanticTokenType::TYPE,
SemanticTokenModifier::DECLARATION,
);
}
}
fn enrich_modifiers_from_decl(
semantic_model: &glua_code_analysis::SemanticModel,
semantic_decl: &glua_code_analysis::LuaSemanticDeclId,
decl_type: &glua_code_analysis::LuaType,
modifiers: &mut Vec<lsp_types::SemanticTokenModifier>,
) {
let enrich_from_property =
|property_id: &glua_code_analysis::LuaSemanticDeclId,
mods: &mut Vec<lsp_types::SemanticTokenModifier>| {
if let Some(property) = semantic_model
.get_db()
.get_property_index()
.get_property(property_id)
{
if property.deprecated().is_some() {
mods.push(lsp_types::SemanticTokenModifier::DEPRECATED);
}
if property
.decl_features
.has_feature(glua_code_analysis::PropertyDeclFeature::ReadOnly)
{
mods.push(lsp_types::SemanticTokenModifier::READONLY);
}
}
};
enrich_from_property(semantic_decl, modifiers);
if let glua_code_analysis::LuaType::Signature(signature_id) = decl_type {
if let Some(signature) = semantic_model
.get_db()
.get_signature_index()
.get(signature_id)
{
if signature.async_state == glua_code_analysis::AsyncState::Async {
modifiers.push(lsp_types::SemanticTokenModifier::ASYNC);
}
}
let sig_decl_id = glua_code_analysis::LuaSemanticDeclId::Signature(*signature_id);
if sig_decl_id != *semantic_decl {
enrich_from_property(&sig_decl_id, modifiers);
}
}
}