Skip to content

Commit 82dbf55

Browse files
committed
add diagnostic prog macro, add more doc completion
1 parent 8f40076 commit 82dbf55

File tree

7 files changed

+274
-69
lines changed

7 files changed

+274
-69
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/code_analysis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66
[dependencies]
77
url = "2.5.2"
88
emmylua_parser = { path = "../emmylua_parser" }
9+
diagnostic_macro = { path = "../diagnostic_macro" }
910
globset = "0.4.15"
1011
percent-encoding = "2.3"
1112
flagset = "0.4.6"

crates/code_analysis/src/diagnostic/lua_diagnostic_code.rs

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use std::{fmt, str::FromStr};
2-
1+
use diagnostic_macro::LuaDiagnosticMacro;
32
use lsp_types::DiagnosticSeverity;
43
use schemars::JsonSchema;
54
use serde::{Deserialize, Serialize};
65

7-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, LuaDiagnosticMacro)]
87
#[serde(rename_all = "kebab-case")]
98
pub enum DiagnosticCode {
109
None,
@@ -30,72 +29,6 @@ pub enum DiagnosticCode {
3029
// ... other variants
3130
}
3231

33-
impl DiagnosticCode {
34-
pub fn get_name(&self) -> &str {
35-
match self {
36-
DiagnosticCode::None => "none",
37-
DiagnosticCode::SyntaxError => "syntax-error",
38-
DiagnosticCode::TypeNotFound => "type-not-found",
39-
DiagnosticCode::MissingReturn => "missing-return",
40-
DiagnosticCode::TypeNotMatch => "type-not-match",
41-
DiagnosticCode::MissingParameter => "missing-parameter",
42-
DiagnosticCode::InjectFieldFail => "inject-field-fail",
43-
DiagnosticCode::UnreachableCode => "unreachable-code",
44-
DiagnosticCode::Unused => "unused",
45-
DiagnosticCode::UndefinedGlobal => "undefined-global",
46-
DiagnosticCode::NeedImport => "need-import",
47-
DiagnosticCode::Deprecated => "deprecated",
48-
DiagnosticCode::AccessPrivateMember => "access-private-member",
49-
DiagnosticCode::AccessProtectedMember => "access-protected-member",
50-
DiagnosticCode::AccessPackageMember => "access-package-member",
51-
DiagnosticCode::NoDiscard => "no-discard",
52-
DiagnosticCode::DisableGlobalDefine => "disable-global-define",
53-
DiagnosticCode::UndefinedField => "undefined-field",
54-
DiagnosticCode::LocalConstReassign => "local-const-reassign",
55-
DiagnosticCode::DuplicateType => "duplicate-type",
56-
// ... handle other variants
57-
}
58-
}
59-
}
60-
61-
// Implement FromStr for DiagnosticCode
62-
impl FromStr for DiagnosticCode {
63-
type Err = ();
64-
65-
fn from_str(s: &str) -> Result<Self, Self::Err> {
66-
match s {
67-
"syntax-error" => Ok(DiagnosticCode::SyntaxError),
68-
"type-not-found" => Ok(DiagnosticCode::TypeNotFound),
69-
"missing-return" => Ok(DiagnosticCode::MissingReturn),
70-
"type-not-match" => Ok(DiagnosticCode::TypeNotMatch),
71-
"missing-parameter" => Ok(DiagnosticCode::MissingParameter),
72-
"inject-field-fail" => Ok(DiagnosticCode::InjectFieldFail),
73-
"unreachable-code" => Ok(DiagnosticCode::UnreachableCode),
74-
"unused" => Ok(DiagnosticCode::Unused),
75-
"undefined-global" => Ok(DiagnosticCode::UndefinedGlobal),
76-
"need-import" => Ok(DiagnosticCode::NeedImport),
77-
"deprecated" => Ok(DiagnosticCode::Deprecated),
78-
"access-private-member" => Ok(DiagnosticCode::AccessPrivateMember),
79-
"access-protected-member" => Ok(DiagnosticCode::AccessProtectedMember),
80-
"access-package-member" => Ok(DiagnosticCode::AccessPackageMember),
81-
"no-discard" => Ok(DiagnosticCode::NoDiscard),
82-
"disable-global-define" => Ok(DiagnosticCode::DisableGlobalDefine),
83-
"undefined-field" => Ok(DiagnosticCode::UndefinedField),
84-
"local-const-reassign" => Ok(DiagnosticCode::LocalConstReassign),
85-
"duplicate-type" => Ok(DiagnosticCode::DuplicateType),
86-
// ... handle other variants
87-
_ => Ok(DiagnosticCode::None),
88-
}
89-
}
90-
}
91-
92-
// Implement Display for DiagnosticCode
93-
impl fmt::Display for DiagnosticCode {
94-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95-
write!(f, "{}", self.get_name())
96-
}
97-
}
98-
9932
// Update functions to match enum variants
10033
pub fn get_default_severity(code: DiagnosticCode) -> DiagnosticSeverity {
10134
match code {

crates/diagnostic_macro/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "diagnostic_macro"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
proc-macro2 = "1.0"
11+
syn = "2.0"
12+
quote = "1.0"

crates/diagnostic_macro/src/lib.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
extern crate proc_macro;
2+
3+
use proc_macro::TokenStream;
4+
use quote::quote;
5+
use syn::{
6+
parse_macro_input, DeriveInput, Data, Fields, Ident,
7+
spanned::Spanned,
8+
};
9+
10+
// Convert enum variant names to kebab-case
11+
fn to_kebab_case(ident: &Ident) -> String {
12+
let s = ident.to_string();
13+
let mut result = String::new();
14+
for (i, c) in s.chars().enumerate() {
15+
if c.is_uppercase() && i != 0 {
16+
result.push('-');
17+
result.push(c.to_ascii_lowercase());
18+
} else {
19+
result.push(c.to_ascii_lowercase());
20+
}
21+
}
22+
result
23+
}
24+
25+
#[proc_macro_derive(LuaDiagnosticMacro)]
26+
pub fn lua_diagnostic_macro(input: TokenStream) -> TokenStream {
27+
// Parse the input TokenStream into DeriveInput
28+
let input = parse_macro_input!(input as DeriveInput);
29+
let name = input.ident.clone();
30+
31+
// Ensure the input is an enum
32+
let variants = match input.data {
33+
Data::Enum(ref data_enum) => &data_enum.variants,
34+
_ => {
35+
return syn::Error::new(
36+
input.span(),
37+
"LuaDiagnosticMacro only supports enums"
38+
)
39+
.to_compile_error()
40+
.into();
41+
}
42+
};
43+
44+
// Generate get_name / FromStr / Display / all based on variants
45+
let mut variant_idents = Vec::new();
46+
let mut variant_strings = Vec::new();
47+
48+
for variant in variants.iter() {
49+
let variant_ident = &variant.ident;
50+
// Handle unit variants
51+
if let Fields::Unit = &variant.fields {
52+
let kebab_case_string = to_kebab_case(variant_ident);
53+
variant_idents.push(variant_ident);
54+
variant_strings.push(kebab_case_string);
55+
} else {
56+
// Only unit variants are supported
57+
return syn::Error::new(variant.ident.span(), "Only unit variants supported")
58+
.to_compile_error()
59+
.into();
60+
}
61+
}
62+
63+
// Build match arms for get_name()
64+
let get_name_arms = variant_idents.iter().zip(variant_strings.iter()).map(|(ident, kc)| {
65+
quote! {
66+
#name::#ident => #kc
67+
}
68+
});
69+
70+
// Build match arms for FromStr
71+
let from_str_arms = variant_idents.iter().zip(variant_strings.iter()).map(|(ident, kc)| {
72+
quote! {
73+
#kc => Ok(#name::#ident)
74+
}
75+
});
76+
77+
// Build the all() array
78+
let all_variants = variant_idents.iter().map(|ident| {
79+
quote! { #name::#ident }
80+
});
81+
82+
// Generate the complete impl block
83+
let expanded = quote! {
84+
impl #name {
85+
pub fn get_name(&self) -> &str {
86+
match self {
87+
#(#get_name_arms),*,
88+
_ => "none"
89+
}
90+
}
91+
92+
// Return all variants
93+
pub fn all() -> Vec<#name> {
94+
vec![
95+
#(#all_variants),*
96+
]
97+
}
98+
}
99+
100+
impl ::std::str::FromStr for #name {
101+
type Err = ();
102+
103+
fn from_str(s: &str) -> Result<Self, Self::Err> {
104+
match s {
105+
#(#from_str_arms),*,
106+
_ => Ok(#name::None),
107+
}
108+
}
109+
}
110+
111+
impl ::std::fmt::Display for #name {
112+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
113+
write!(f, "{}", self.get_name())
114+
}
115+
}
116+
};
117+
118+
expanded.into()
119+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use std::collections::HashSet;
2+
3+
use code_analysis::DiagnosticCode;
4+
use emmylua_parser::{
5+
LuaAstNode, LuaClosureExpr, LuaComment, LuaDocTagParam, LuaSyntaxKind,
6+
LuaTokenKind,
7+
};
8+
use lsp_types::CompletionItem;
9+
10+
use crate::handlers::completion::completion_builder::CompletionBuilder;
11+
12+
pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
13+
if builder.is_cancelled() {
14+
return None;
15+
}
16+
17+
let trigger_token = &builder.trigger_token;
18+
if !matches!(trigger_token.kind().into(), LuaTokenKind::TkName) {
19+
return None;
20+
}
21+
22+
let parent_node = trigger_token.parent()?;
23+
match parent_node.kind().into() {
24+
LuaSyntaxKind::DocTagParam => {
25+
add_tag_param_name_completion(builder, LuaDocTagParam::cast(parent_node)?);
26+
}
27+
LuaSyntaxKind::DocTagCast => {
28+
add_tag_cast_name_completion(builder);
29+
}
30+
LuaSyntaxKind::DocTagDiagnostic => {
31+
add_tag_diagnostic_action_completion(builder);
32+
}
33+
LuaSyntaxKind::DocDiagnosticCodeList => {
34+
add_tag_diagnostic_code_completion(builder);
35+
}
36+
_ => {}
37+
}
38+
39+
builder.stop_here();
40+
41+
Some(())
42+
}
43+
44+
fn add_tag_param_name_completion(
45+
builder: &mut CompletionBuilder,
46+
node: LuaDocTagParam,
47+
) -> Option<()> {
48+
let comment = node.ancestors::<LuaComment>().next()?;
49+
let owner = comment.get_owner()?;
50+
let closure = owner.descendants::<LuaClosureExpr>().next()?;
51+
let params = closure.get_params_list()?.get_params();
52+
for param in params {
53+
let completion_item = CompletionItem {
54+
label: param.get_name_token()?.get_name_text().to_string(),
55+
kind: Some(lsp_types::CompletionItemKind::VARIABLE),
56+
..Default::default()
57+
};
58+
59+
builder.add_completion_item(completion_item);
60+
}
61+
62+
Some(())
63+
}
64+
65+
fn add_tag_cast_name_completion(
66+
builder: &mut CompletionBuilder,
67+
) -> Option<()> {
68+
let file_id = builder.semantic_model.get_file_id();
69+
let decl_tree = builder
70+
.semantic_model
71+
.get_db()
72+
.get_decl_index()
73+
.get_decl_tree(&file_id)?;
74+
let mut duplicated_name = HashSet::new();
75+
let local_env = decl_tree.get_env_decls(builder.trigger_token.text_range().start())?;
76+
for decl_id in local_env.iter() {
77+
let name = {
78+
let decl = builder
79+
.semantic_model
80+
.get_db()
81+
.get_decl_index()
82+
.get_decl(&decl_id)?;
83+
84+
decl.get_name().to_string()
85+
};
86+
if duplicated_name.contains(&name) {
87+
continue;
88+
}
89+
90+
duplicated_name.insert(name.clone());
91+
let completion_item = CompletionItem {
92+
label: name,
93+
kind: Some(lsp_types::CompletionItemKind::VARIABLE),
94+
..Default::default()
95+
};
96+
builder.add_completion_item(completion_item);
97+
}
98+
99+
Some(())
100+
}
101+
102+
fn add_tag_diagnostic_action_completion(builder: &mut CompletionBuilder) {
103+
let actions = vec!["disable", "disable-next-line"];
104+
for (sorted_index, action) in actions.iter().enumerate() {
105+
let completion_item = CompletionItem {
106+
label: action.to_string(),
107+
kind: Some(lsp_types::CompletionItemKind::EVENT),
108+
sort_text: Some(format!("{:03}", sorted_index)),
109+
..Default::default()
110+
};
111+
112+
builder.add_completion_item(completion_item);
113+
}
114+
}
115+
116+
fn add_tag_diagnostic_code_completion(builder: &mut CompletionBuilder) {
117+
let codes = DiagnosticCode::all();
118+
for (sorted_index, code) in codes.iter().enumerate() {
119+
let completion_item = CompletionItem {
120+
label: code.get_name().to_string(),
121+
kind: Some(lsp_types::CompletionItemKind::EVENT),
122+
sort_text: Some(format!("{:03}", sorted_index)),
123+
..Default::default()
124+
};
125+
126+
builder.add_completion_item(completion_item);
127+
}
128+
}

crates/emmylua_ls/src/handlers/completion/providers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod file_path_provider;
55
mod keywords_provider;
66
mod member_provider;
77
mod module_path_provider;
8+
mod doc_name_token_provider;
89

910
use super::completion_builder::CompletionBuilder;
1011

@@ -16,6 +17,7 @@ pub fn add_completions(builder: &mut CompletionBuilder) -> Option<()> {
1617
member_provider::add_completion(builder);
1718
doc_tag_provider::add_completion(builder);
1819
doc_type_provider::add_completion(builder);
20+
doc_name_token_provider::add_completion(builder);
1921

2022
Some(())
2123
}

0 commit comments

Comments
 (0)