From 32a9f327e071ae465da1ac7b62307fd676963608 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 23 Sep 2025 05:18:56 +0800 Subject: [PATCH 01/30] Add new feature `attribute` AST parsing --- crates/emmylua_parser/src/grammar/doc/tag.rs | 129 +++++++++++++++++- crates/emmylua_parser/src/grammar/doc/test.rs | 57 ++++++++ .../emmylua_parser/src/grammar/doc/types.rs | 2 +- .../src/kind/lua_syntax_kind.rs | 7 +- .../emmylua_parser/src/kind/lua_token_kind.rs | 2 + .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 72 +++++++++- .../src/parser/lua_doc_parser.rs | 5 +- 7 files changed, 269 insertions(+), 5 deletions(-) diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index b2441390c..54a07df6d 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -8,7 +8,7 @@ use crate::{ use super::{ expect_token, if_token_bump, parse_description, - types::{parse_fun_type, parse_type, parse_type_list}, + types::{parse_fun_type, parse_type, parse_type_list, parse_typed_param}, }; pub fn parse_tag(p: &mut LuaDocParser) { @@ -57,6 +57,8 @@ fn parse_tag_detail(p: &mut LuaDocParser) -> DocParseResult { LuaTokenKind::TkTagMeta => parse_tag_meta(p), LuaTokenKind::TkTagExport => parse_tag_export(p), LuaTokenKind::TkLanguage => parse_tag_language(p), + LuaTokenKind::TkTagAttribute => parse_tag_attribute_def(p), + LuaTokenKind::TkDocAttributeStart => parse_attribute_usage(p), // simple tag LuaTokenKind::TkTagVisibility => parse_tag_simple(p, LuaSyntaxKind::DocTagVisibility), @@ -645,3 +647,128 @@ fn parse_tag_language(p: &mut LuaDocParser) -> DocParseResult { parse_description(p); Ok(m.complete(p)) } + +// ---@attribute 名称(参数列表) +fn parse_tag_attribute_def(p: &mut LuaDocParser) -> DocParseResult { + p.set_state(LuaDocLexerState::Normal); + let m = p.mark(LuaSyntaxKind::DocTagAttributeDef); + p.bump(); + + // 解析属性名称 + expect_token(p, LuaTokenKind::TkName)?; + + // 解析参数列表 + expect_token(p, LuaTokenKind::TkLeftParen)?; + + if p.current_token() != LuaTokenKind::TkRightParen { + parse_typed_param(p)?; + while p.current_token() == LuaTokenKind::TkComma { + p.bump(); + parse_typed_param(p)?; + } + } + + expect_token(p, LuaTokenKind::TkRightParen)?; + + p.set_state(LuaDocLexerState::Description); + parse_description(p); + Ok(m.complete(p)) +} + +// ---@[attribute_name(params)] or ---@[attribute_name] +pub fn parse_attribute_usage(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocAttributeUsage); + p.bump(); // consume '[' + + // 解析属性名称 + parse_attribute_name(p)?; + + // 解析参数列表, 允许没有参数的特性在使用时省略括号 + if p.current_token() == LuaTokenKind::TkLeftParen { + parse_attribute_arg_list(p)?; + } + + // 期望结束符号 ']' + expect_token(p, LuaTokenKind::TkRightBracket)?; + + // 属性使用解析完成后, 重置状态 + p.set_state(LuaDocLexerState::Description); + + Ok(m.complete(p)) +} + +// 解析属性名称 +fn parse_attribute_name(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocAttributeName); + expect_token(p, LuaTokenKind::TkName)?; + Ok(m.complete(p)) +} + +// 解析属性参数列表 +fn parse_attribute_arg_list(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocAttributeArgList); + p.bump(); // consume '(' + + // 解析参数值列表 + while p.current_token() != LuaTokenKind::TkRightParen + && p.current_token() != LuaTokenKind::TkEof + { + // 跳过空白字符 + while p.current_token() == LuaTokenKind::TkWhitespace { + p.bump(); + } + + // 如果遇到右括号则跳出 + if p.current_token() == LuaTokenKind::TkRightParen { + break; + } + + // 解析单个参数 + parse_attribute_arg(p)?; + + // 跳过空白字符 + while p.current_token() == LuaTokenKind::TkWhitespace { + p.bump(); + } + + // 处理逗号分隔符 + if p.current_token() == LuaTokenKind::TkComma { + p.bump(); + } else if p.current_token() != LuaTokenKind::TkRightParen { + break; + } + } + + expect_token(p, LuaTokenKind::TkRightParen)?; + Ok(m.complete(p)) +} + +// 解析单个属性参数 +fn parse_attribute_arg(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocAttributeArg); + + // TODO: 添加具名参数支持(name = value) + parse_attribute_arg_value(p)?; + + Ok(m.complete(p)) +} + +// 解析属性参数值 +fn parse_attribute_arg_value(p: &mut LuaDocParser) -> Result<(), LuaParseError> { + match p.current_token() { + LuaTokenKind::TkString + | LuaTokenKind::TkLongString + | LuaTokenKind::TkInt + | LuaTokenKind::TkFloat + | LuaTokenKind::TkTrue + | LuaTokenKind::TkFalse + | LuaTokenKind::TkName => { + p.bump(); + Ok(()) + } + _ => Err(LuaParseError::doc_error_from( + "Expected attribute argument value", + p.current_token_range(), + )), + } +} diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 91a0b09c5..bfd78ffbe 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2867,4 +2867,61 @@ Syntax(Chunk)@0..137 assert_ast_eq!(code, result); } + + #[test] + fn test_attribute_doc() { + let code = r#" + ---@attribute check_point(x: string, y: number) + ---@[check_point("a", 0)] + "#; + // print_ast(code); + let result = r#" +Syntax(Chunk)@0..99 + Syntax(Block)@0..99 + Token(TkEndOfLine)@0..1 "\n" + Token(TkWhitespace)@1..9 " " + Syntax(Comment)@9..90 + Token(TkDocStart)@9..13 "---@" + Syntax(DocTagAttributeDef)@13..56 + Token(TkTagAttribute)@13..22 "attribute" + Token(TkWhitespace)@22..23 " " + Token(TkName)@23..34 "check_point" + Token(TkLeftParen)@34..35 "(" + Syntax(DocTypedParameter)@35..44 + Token(TkName)@35..36 "x" + Token(TkColon)@36..37 ":" + Token(TkWhitespace)@37..38 " " + Syntax(TypeName)@38..44 + Token(TkName)@38..44 "string" + Token(TkComma)@44..45 "," + Token(TkWhitespace)@45..46 " " + Syntax(DocTypedParameter)@46..55 + Token(TkName)@46..47 "y" + Token(TkColon)@47..48 ":" + Token(TkWhitespace)@48..49 " " + Syntax(TypeName)@49..55 + Token(TkName)@49..55 "number" + Token(TkRightParen)@55..56 ")" + Token(TkEndOfLine)@56..57 "\n" + Token(TkWhitespace)@57..65 " " + Token(TkDocStart)@65..69 "---@" + Syntax(DocAttributeUsage)@69..90 + Token(TkDocAttributeStart)@69..70 "[" + Syntax(DocAttributeName)@70..81 + Token(TkName)@70..81 "check_point" + Syntax(DocAttributeArgList)@81..89 + Token(TkLeftParen)@81..82 "(" + Syntax(DocAttributeArg)@82..85 + Token(TkString)@82..85 "\"a\"" + Token(TkComma)@85..86 "," + Token(TkWhitespace)@86..87 " " + Syntax(DocAttributeArg)@87..88 + Token(TkInt)@87..88 "0" + Token(TkRightParen)@88..89 ")" + Token(TkRightBracket)@89..90 "]" + Token(TkEndOfLine)@90..91 "\n" + Token(TkWhitespace)@91..99 " " + "#; + assert_ast_eq!(code, result); + } } diff --git a/crates/emmylua_parser/src/grammar/doc/types.rs b/crates/emmylua_parser/src/grammar/doc/types.rs index 2cbfcfe7f..45c5e0d8f 100644 --- a/crates/emmylua_parser/src/grammar/doc/types.rs +++ b/crates/emmylua_parser/src/grammar/doc/types.rs @@ -314,7 +314,7 @@ fn parse_fun_return_type(p: &mut LuaDocParser) -> DocParseResult { // ... : // // ... -fn parse_typed_param(p: &mut LuaDocParser) -> DocParseResult { +pub fn parse_typed_param(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTypedParameter); match p.current_token() { LuaTokenKind::TkName => { diff --git a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs index 6f6c7d1d2..8c5f25830 100644 --- a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs @@ -92,6 +92,7 @@ pub enum LuaSyntaxKind { DocTagReturnCast, DocTagExport, DocTagLanguage, + DocTagAttributeDef, // doc Type TypeArray, // baseType [] @@ -124,7 +125,11 @@ pub enum LuaSyntaxKind { DocGenericDeclareList, DocDiagnosticNameList, DocTypeList, - DocAttribute, + DocAttribute, // (partial, global, local, ...) + DocAttributeUsage, // '@[attribute_name(params)]' or '@[attribute_name]' + DocAttributeName, // attribute_name in @[attribute_name(...)] + DocAttributeArgList, // argument list in @[attribute_name(arg1, arg2, ...)] + DocAttributeArg, // single argument in attribute usage DocOpType, // +, -, +? DocMappedKeys, // [p in KeyType]? DocEnumFieldList, // ---| diff --git a/crates/emmylua_parser/src/kind/lua_token_kind.rs b/crates/emmylua_parser/src/kind/lua_token_kind.rs index c0e11dde3..5bb8be64d 100644 --- a/crates/emmylua_parser/src/kind/lua_token_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_token_kind.rs @@ -135,6 +135,7 @@ pub enum LuaTokenKind { TkTagReturnCast, // return cast TkTagExport, // export TkLanguage, // language + TkTagAttribute, // attribute TkDocOr, // | TkDocAnd, // & @@ -157,6 +158,7 @@ pub enum LuaTokenKind { TkDocRegion, // region TkDocEndRegion, // endregion TkDocSeeContent, // see content + TkDocAttributeStart, // '@[', used for attribute start } impl fmt::Display for LuaTokenKind { diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index 8adb74d29..6b966cf3e 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -27,6 +27,7 @@ pub enum LuaDocLexerState { Source, NormalDescription, CastExpr, + AttributeUsage, } impl LuaDocLexer<'_> { @@ -73,6 +74,7 @@ impl LuaDocLexer<'_> { LuaDocLexerState::Source => self.lex_source(), LuaDocLexerState::NormalDescription => self.lex_normal_description(), LuaDocLexerState::CastExpr => self.lex_cast_expr(), + LuaDocLexerState::AttributeUsage => self.lex_attribute_usage(), } } @@ -142,6 +144,11 @@ impl LuaDocLexer<'_> { reader.eat_while(is_doc_whitespace); LuaTokenKind::TkWhitespace } + '[' => { + reader.bump(); + self.state = LuaDocLexerState::AttributeUsage; + LuaTokenKind::TkDocAttributeStart + } ch if is_name_start(ch) => { reader.bump(); reader.eat_while(is_name_continue); @@ -272,10 +279,21 @@ impl LuaDocLexer<'_> { _ => LuaTokenKind::TkDocTrivia, } } - '#' | '@' => { + '#' => { reader.eat_while(|_| true); LuaTokenKind::TkDocDetail } + '@' => { + reader.bump(); + // 需要检查是否在使用 Attribute 语法 + if reader.current_char() == '[' { + reader.bump(); + LuaTokenKind::TkDocAttributeStart + } else { + reader.eat_while(|_| true); + LuaTokenKind::TkDocDetail + } + } ch if ch.is_ascii_digit() => { reader.eat_while(|ch| ch.is_ascii_digit()); LuaTokenKind::TkInt @@ -581,6 +599,57 @@ impl LuaDocLexer<'_> { _ => self.lex_normal(), } } + + fn lex_attribute_usage(&mut self) -> LuaTokenKind { + let reader = self.reader.as_mut().unwrap(); + match reader.current_char() { + ch if is_doc_whitespace(ch) => { + reader.eat_while(is_doc_whitespace); + LuaTokenKind::TkWhitespace + } + '(' => { + reader.bump(); + LuaTokenKind::TkLeftParen + } + ')' => { + reader.bump(); + LuaTokenKind::TkRightParen + } + ',' => { + reader.bump(); + LuaTokenKind::TkComma + } + '=' => { + reader.bump(); + LuaTokenKind::TkAssign + } + ']' => { + reader.bump(); + LuaTokenKind::TkRightBracket + } + ch if ch == '"' || ch == '\'' => { + reader.bump(); + reader.eat_while(|c| c != ch); + if reader.current_char() == ch { + reader.bump(); + } + LuaTokenKind::TkString + } + ch if ch.is_ascii_digit() => { + reader.eat_while(|ch| ch.is_ascii_digit()); + LuaTokenKind::TkInt + } + ch if is_name_start(ch) => { + reader.bump(); + reader.eat_while(is_name_continue); + LuaTokenKind::TkName + } + _ => { + reader.bump(); + LuaTokenKind::TkDocTrivia + } + } + } } fn to_tag(text: &str) -> LuaTokenKind { @@ -617,6 +686,7 @@ fn to_tag(text: &str) -> LuaTokenKind { "source" => LuaTokenKind::TkTagSource, "export" => LuaTokenKind::TkTagExport, "language" => LuaTokenKind::TkLanguage, + "attribute" => LuaTokenKind::TkTagAttribute, _ => LuaTokenKind::TkTagOther, } } diff --git a/crates/emmylua_parser/src/parser/lua_doc_parser.rs b/crates/emmylua_parser/src/parser/lua_doc_parser.rs index 404833bd1..287cf6839 100644 --- a/crates/emmylua_parser/src/parser/lua_doc_parser.rs +++ b/crates/emmylua_parser/src/parser/lua_doc_parser.rs @@ -91,7 +91,10 @@ impl<'b> LuaDocParser<'_, 'b> { self.eat_current_and_lex_next(); } } - LuaDocLexerState::FieldStart | LuaDocLexerState::See | LuaDocLexerState::Source => { + LuaDocLexerState::FieldStart + | LuaDocLexerState::See + | LuaDocLexerState::Source + | LuaDocLexerState::AttributeUsage => { while matches!(self.current_token, LuaTokenKind::TkWhitespace) { self.eat_current_and_lex_next(); } From 6942c4e149f586f6611cbd0616ef7dc6500c98e8 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 23 Sep 2025 21:05:35 +0800 Subject: [PATCH 02/30] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/emmylua_parser/src/grammar/doc/tag.rs | 78 ++++++++----------- crates/emmylua_parser/src/grammar/doc/test.rs | 44 ++++++----- .../src/kind/lua_syntax_kind.rs | 4 +- .../emmylua_parser/src/kind/lua_token_kind.rs | 2 +- .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 4 +- .../emmylua_parser/src/syntax/node/doc/tag.rs | 39 ++++++++++ crates/emmylua_parser/src/syntax/node/mod.rs | 5 ++ 7 files changed, 105 insertions(+), 71 deletions(-) diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 54a07df6d..17fbbe5ad 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -57,8 +57,8 @@ fn parse_tag_detail(p: &mut LuaDocParser) -> DocParseResult { LuaTokenKind::TkTagMeta => parse_tag_meta(p), LuaTokenKind::TkTagExport => parse_tag_export(p), LuaTokenKind::TkLanguage => parse_tag_language(p), - LuaTokenKind::TkTagAttribute => parse_tag_attribute_def(p), - LuaTokenKind::TkDocAttributeStart => parse_attribute_usage(p), + LuaTokenKind::TkTagAttribute => parse_tag_attribute(p), + LuaTokenKind::TkDocAttribute => parse_attribute_usage(p), // simple tag LuaTokenKind::TkTagVisibility => parse_tag_simple(p, LuaSyntaxKind::DocTagVisibility), @@ -86,7 +86,7 @@ fn parse_tag_class(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagClass); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_tag_attribute(p)?; + parse_doc_attribute(p)?; } expect_token(p, LuaTokenKind::TkName)?; @@ -106,7 +106,7 @@ fn parse_tag_class(p: &mut LuaDocParser) -> DocParseResult { } // (partial, global, local) -fn parse_tag_attribute(p: &mut LuaDocParser) -> DocParseResult { +fn parse_doc_attribute(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocAttribute); p.bump(); expect_token(p, LuaTokenKind::TkName)?; @@ -160,7 +160,7 @@ fn parse_tag_enum(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagEnum); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_tag_attribute(p)?; + parse_doc_attribute(p)?; } expect_token(p, LuaTokenKind::TkName)?; @@ -247,7 +247,7 @@ fn parse_tag_field(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagField); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_tag_attribute(p)?; + parse_doc_attribute(p)?; } p.set_state(LuaDocLexerState::Normal); @@ -649,15 +649,25 @@ fn parse_tag_language(p: &mut LuaDocParser) -> DocParseResult { } // ---@attribute 名称(参数列表) -fn parse_tag_attribute_def(p: &mut LuaDocParser) -> DocParseResult { +fn parse_tag_attribute(p: &mut LuaDocParser) -> DocParseResult { p.set_state(LuaDocLexerState::Normal); - let m = p.mark(LuaSyntaxKind::DocTagAttributeDef); + let m = p.mark(LuaSyntaxKind::DocTagAttribute); p.bump(); // 解析属性名称 expect_token(p, LuaTokenKind::TkName)?; // 解析参数列表 + parse_type_attribute(p)?; + + p.set_state(LuaDocLexerState::Description); + parse_description(p); + Ok(m.complete(p)) +} + +// (param1: type1, param2: type2, ...) +fn parse_type_attribute(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::TypeAttribute); expect_token(p, LuaTokenKind::TkLeftParen)?; if p.current_token() != LuaTokenKind::TkRightParen { @@ -669,9 +679,6 @@ fn parse_tag_attribute_def(p: &mut LuaDocParser) -> DocParseResult { } expect_token(p, LuaTokenKind::TkRightParen)?; - - p.set_state(LuaDocLexerState::Description); - parse_description(p); Ok(m.complete(p)) } @@ -680,8 +687,7 @@ pub fn parse_attribute_usage(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocAttributeUsage); p.bump(); // consume '[' - // 解析属性名称 - parse_attribute_name(p)?; + expect_token(p, LuaTokenKind::TkName)?; // 解析参数列表, 允许没有参数的特性在使用时省略括号 if p.current_token() == LuaTokenKind::TkLeftParen { @@ -697,45 +703,25 @@ pub fn parse_attribute_usage(p: &mut LuaDocParser) -> DocParseResult { Ok(m.complete(p)) } -// 解析属性名称 -fn parse_attribute_name(p: &mut LuaDocParser) -> DocParseResult { - let m = p.mark(LuaSyntaxKind::DocAttributeName); - expect_token(p, LuaTokenKind::TkName)?; - Ok(m.complete(p)) -} - // 解析属性参数列表 fn parse_attribute_arg_list(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocAttributeArgList); p.bump(); // consume '(' // 解析参数值列表 - while p.current_token() != LuaTokenKind::TkRightParen - && p.current_token() != LuaTokenKind::TkEof - { - // 跳过空白字符 - while p.current_token() == LuaTokenKind::TkWhitespace { - p.bump(); - } - - // 如果遇到右括号则跳出 - if p.current_token() == LuaTokenKind::TkRightParen { - break; - } - - // 解析单个参数 - parse_attribute_arg(p)?; - - // 跳过空白字符 - while p.current_token() == LuaTokenKind::TkWhitespace { - p.bump(); - } - - // 处理逗号分隔符 - if p.current_token() == LuaTokenKind::TkComma { - p.bump(); - } else if p.current_token() != LuaTokenKind::TkRightParen { - break; + if p.current_token() != LuaTokenKind::TkRightParen { + loop { + if p.current_token() == LuaTokenKind::TkEof { + break; + } + parse_attribute_arg(p)?; + if p.current_token() != LuaTokenKind::TkComma { + break; + } + p.bump(); // consume comma + if p.current_token() == LuaTokenKind::TkRightParen { + break; // trailing comma + } } } diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index bfd78ffbe..9488c3e47 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2875,6 +2875,10 @@ Syntax(Chunk)@0..137 ---@[check_point("a", 0)] "#; // print_ast(code); + // print_ast(r#" + // ---@alias a fun(x: string, y: number) + // check_point("a", 0) + // "#); let result = r#" Syntax(Chunk)@0..99 Syntax(Block)@0..99 @@ -2882,33 +2886,33 @@ Syntax(Chunk)@0..99 Token(TkWhitespace)@1..9 " " Syntax(Comment)@9..90 Token(TkDocStart)@9..13 "---@" - Syntax(DocTagAttributeDef)@13..56 + Syntax(DocTagAttribute)@13..56 Token(TkTagAttribute)@13..22 "attribute" Token(TkWhitespace)@22..23 " " Token(TkName)@23..34 "check_point" - Token(TkLeftParen)@34..35 "(" - Syntax(DocTypedParameter)@35..44 - Token(TkName)@35..36 "x" - Token(TkColon)@36..37 ":" - Token(TkWhitespace)@37..38 " " - Syntax(TypeName)@38..44 - Token(TkName)@38..44 "string" - Token(TkComma)@44..45 "," - Token(TkWhitespace)@45..46 " " - Syntax(DocTypedParameter)@46..55 - Token(TkName)@46..47 "y" - Token(TkColon)@47..48 ":" - Token(TkWhitespace)@48..49 " " - Syntax(TypeName)@49..55 - Token(TkName)@49..55 "number" - Token(TkRightParen)@55..56 ")" + Syntax(TypeAttribute)@34..56 + Token(TkLeftParen)@34..35 "(" + Syntax(DocTypedParameter)@35..44 + Token(TkName)@35..36 "x" + Token(TkColon)@36..37 ":" + Token(TkWhitespace)@37..38 " " + Syntax(TypeName)@38..44 + Token(TkName)@38..44 "string" + Token(TkComma)@44..45 "," + Token(TkWhitespace)@45..46 " " + Syntax(DocTypedParameter)@46..55 + Token(TkName)@46..47 "y" + Token(TkColon)@47..48 ":" + Token(TkWhitespace)@48..49 " " + Syntax(TypeName)@49..55 + Token(TkName)@49..55 "number" + Token(TkRightParen)@55..56 ")" Token(TkEndOfLine)@56..57 "\n" Token(TkWhitespace)@57..65 " " Token(TkDocStart)@65..69 "---@" Syntax(DocAttributeUsage)@69..90 - Token(TkDocAttributeStart)@69..70 "[" - Syntax(DocAttributeName)@70..81 - Token(TkName)@70..81 "check_point" + Token(TkDocAttribute)@69..70 "[" + Token(TkName)@70..81 "check_point" Syntax(DocAttributeArgList)@81..89 Token(TkLeftParen)@81..82 "(" Syntax(DocAttributeArg)@82..85 diff --git a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs index 8c5f25830..8d6a512bc 100644 --- a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs @@ -92,7 +92,7 @@ pub enum LuaSyntaxKind { DocTagReturnCast, DocTagExport, DocTagLanguage, - DocTagAttributeDef, + DocTagAttribute, // doc Type TypeArray, // baseType [] @@ -109,6 +109,7 @@ pub enum LuaSyntaxKind { TypeNullable, // ? TypeStringTemplate, // prefixName.`T` TypeMultiLineUnion, // | simple type # description + TypeAttribute, // attribute(paramList) // follow donot support now TypeMatch, @@ -127,7 +128,6 @@ pub enum LuaSyntaxKind { DocTypeList, DocAttribute, // (partial, global, local, ...) DocAttributeUsage, // '@[attribute_name(params)]' or '@[attribute_name]' - DocAttributeName, // attribute_name in @[attribute_name(...)] DocAttributeArgList, // argument list in @[attribute_name(arg1, arg2, ...)] DocAttributeArg, // single argument in attribute usage DocOpType, // +, -, +? diff --git a/crates/emmylua_parser/src/kind/lua_token_kind.rs b/crates/emmylua_parser/src/kind/lua_token_kind.rs index 5bb8be64d..f84d237a5 100644 --- a/crates/emmylua_parser/src/kind/lua_token_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_token_kind.rs @@ -158,7 +158,7 @@ pub enum LuaTokenKind { TkDocRegion, // region TkDocEndRegion, // endregion TkDocSeeContent, // see content - TkDocAttributeStart, // '@[', used for attribute start + TkDocAttribute, // '@[', used for attribute usage } impl fmt::Display for LuaTokenKind { diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index 6b966cf3e..cc34d9393 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -147,7 +147,7 @@ impl LuaDocLexer<'_> { '[' => { reader.bump(); self.state = LuaDocLexerState::AttributeUsage; - LuaTokenKind::TkDocAttributeStart + LuaTokenKind::TkDocAttribute } ch if is_name_start(ch) => { reader.bump(); @@ -288,7 +288,7 @@ impl LuaDocLexer<'_> { // 需要检查是否在使用 Attribute 语法 if reader.current_char() == '[' { reader.bump(); - LuaTokenKind::TkDocAttributeStart + LuaTokenKind::TkDocAttribute } else { reader.eat_while(|_| true); LuaTokenKind::TkDocDetail diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index 5b6b3edf8..fc557e9d7 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -17,6 +17,7 @@ pub enum LuaDocTag { Class(LuaDocTagClass), Enum(LuaDocTagEnum), Alias(LuaDocTagAlias), + Attribute(LuaDocTagAttribute), Type(LuaDocTagType), Param(LuaDocTagParam), Return(LuaDocTagReturn), @@ -51,6 +52,7 @@ impl LuaAstNode for LuaDocTag { LuaDocTag::Class(it) => it.syntax(), LuaDocTag::Enum(it) => it.syntax(), LuaDocTag::Alias(it) => it.syntax(), + LuaDocTag::Attribute(it) => it.syntax(), LuaDocTag::Type(it) => it.syntax(), LuaDocTag::Param(it) => it.syntax(), LuaDocTag::Return(it) => it.syntax(), @@ -88,6 +90,7 @@ impl LuaAstNode for LuaDocTag { || kind == LuaSyntaxKind::DocTagEnum || kind == LuaSyntaxKind::DocTagAlias || kind == LuaSyntaxKind::DocTagType + || kind == LuaSyntaxKind::DocTagAttribute || kind == LuaSyntaxKind::DocTagParam || kind == LuaSyntaxKind::DocTagReturn || kind == LuaSyntaxKind::DocTagOverload @@ -129,6 +132,9 @@ impl LuaAstNode for LuaDocTag { LuaSyntaxKind::DocTagAlias => { Some(LuaDocTag::Alias(LuaDocTagAlias::cast(syntax).unwrap())) } + LuaSyntaxKind::DocTagAttribute => Some(LuaDocTag::Attribute( + LuaDocTagAttribute::cast(syntax).unwrap(), + )), LuaSyntaxKind::DocTagType => { Some(LuaDocTag::Type(LuaDocTagType::cast(syntax).unwrap())) } @@ -1545,3 +1551,36 @@ impl LuaDocTagLanguage { self.token() } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LuaDocTagAttribute { + syntax: LuaSyntaxNode, +} + +impl LuaAstNode for LuaDocTagAttribute { + fn syntax(&self) -> &LuaSyntaxNode { + &self.syntax + } + + fn can_cast(kind: LuaSyntaxKind) -> bool { + kind == LuaSyntaxKind::DocTagAttribute + } + + fn cast(syntax: LuaSyntaxNode) -> Option { + if Self::can_cast(syntax.kind().into()) { + Some(Self { syntax }) + } else { + None + } + } +} + +impl LuaDocTagAttribute { + pub fn get_name_token(&self) -> Option { + self.token() + } + + pub fn get_type(&self) -> Option { + self.child() + } +} diff --git a/crates/emmylua_parser/src/syntax/node/mod.rs b/crates/emmylua_parser/src/syntax/node/mod.rs index 33084c029..48332698b 100644 --- a/crates/emmylua_parser/src/syntax/node/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/mod.rs @@ -88,6 +88,7 @@ pub enum LuaAst { LuaDocTagAs(LuaDocTagAs), LuaDocTagReturnCast(LuaDocTagReturnCast), LuaDocTagExport(LuaDocTagExport), + LuaDocTagAttribute(LuaDocTagAttribute), LuaDocTagLanguage(LuaDocTagLanguage), // doc description LuaDocDescription(LuaDocDescription), @@ -176,6 +177,7 @@ impl LuaAstNode for LuaAst { LuaAst::LuaDocTagAs(node) => node.syntax(), LuaAst::LuaDocTagReturnCast(node) => node.syntax(), LuaAst::LuaDocTagExport(node) => node.syntax(), + LuaAst::LuaDocTagAttribute(node) => node.syntax(), LuaAst::LuaDocTagLanguage(node) => node.syntax(), LuaAst::LuaDocDescription(node) => node.syntax(), LuaAst::LuaDocNameType(node) => node.syntax(), @@ -359,6 +361,9 @@ impl LuaAstNode for LuaAst { LuaSyntaxKind::DocTagClass => LuaDocTagClass::cast(syntax).map(LuaAst::LuaDocTagClass), LuaSyntaxKind::DocTagEnum => LuaDocTagEnum::cast(syntax).map(LuaAst::LuaDocTagEnum), LuaSyntaxKind::DocTagAlias => LuaDocTagAlias::cast(syntax).map(LuaAst::LuaDocTagAlias), + LuaSyntaxKind::DocTagAttribute => { + LuaDocTagAttribute::cast(syntax).map(LuaAst::LuaDocTagAttribute) + } LuaSyntaxKind::DocTagType => LuaDocTagType::cast(syntax).map(LuaAst::LuaDocTagType), LuaSyntaxKind::DocTagParam => LuaDocTagParam::cast(syntax).map(LuaAst::LuaDocTagParam), LuaSyntaxKind::DocTagReturn => { From 7dd1eb76eff25a11efd19e90e038cfe5ab819fc2 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 24 Sep 2025 14:39:33 +0800 Subject: [PATCH 03/30] impl analyzer `attribute` --- Cargo.lock | 1 + crates/emmylua_code_analysis/Cargo.toml | 2 + .../src/compilation/analyzer/decl/docs.rs | 23 +++- .../src/compilation/analyzer/decl/mod.rs | 3 + .../analyzer/doc/attribute_tags.rs | 110 ++++++++++++++++++ .../compilation/analyzer/doc/infer_type.rs | 45 ++++++- .../src/compilation/analyzer/doc/mod.rs | 3 +- .../src/compilation/analyzer/doc/tags.rs | 32 ++++- .../compilation/analyzer/doc/type_def_tags.rs | 32 ++++- .../compilation/analyzer/doc/type_ref_tags.rs | 4 +- .../src/compilation/test/attribute_test.rs | 41 +++++++ .../src/compilation/test/mod.rs | 1 + .../src/db_index/property/mod.rs | 17 +++ .../src/db_index/property/property.rs | 29 ++++- .../src/db_index/type/type_decl.rs | 13 +++ .../src/db_index/type/types.rs | 31 +++++ crates/emmylua_parser/src/grammar/doc/tag.rs | 67 ++++++----- crates/emmylua_parser/src/grammar/doc/test.rs | 41 ++++--- .../src/kind/lua_syntax_kind.rs | 16 +-- .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 8 +- .../src/parser/lua_doc_parser.rs | 2 +- .../emmylua_parser/src/syntax/node/doc/mod.rs | 76 +++++++++++- .../emmylua_parser/src/syntax/node/doc/tag.rs | 48 +++++++- .../src/syntax/node/doc/types.rs | 41 +++++++ 24 files changed, 605 insertions(+), 81 deletions(-) create mode 100644 crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs create mode 100644 crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs diff --git a/Cargo.lock b/Cargo.lock index 8229d1651..7bf9b3679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -564,6 +564,7 @@ dependencies = [ "emmylua_parser", "encoding_rs", "flagset", + "googletest", "include_dir", "internment", "itertools 0.14.0", diff --git a/crates/emmylua_code_analysis/Cargo.toml b/crates/emmylua_code_analysis/Cargo.toml index 305eb45bf..7e7a67426 100644 --- a/crates/emmylua_code_analysis/Cargo.toml +++ b/crates/emmylua_code_analysis/Cargo.toml @@ -14,6 +14,8 @@ include = [ "src/**", "resources/**", ] +[dev-dependencies] +googletest.workspace = true # Inherit workspace lints configuration [lints] diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs index 09257c380..f64eaacd9 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs @@ -1,6 +1,7 @@ use emmylua_parser::{ LuaAstNode, LuaAstToken, LuaComment, LuaDocAttribute, LuaDocTag, LuaDocTagAlias, - LuaDocTagClass, LuaDocTagEnum, LuaDocTagMeta, LuaDocTagNamespace, LuaDocTagUsing, + LuaDocTagAttribute, LuaDocTagClass, LuaDocTagEnum, LuaDocTagMeta, LuaDocTagNamespace, + LuaDocTagUsing, }; use flagset::FlagSet; use rowan::TextRange; @@ -83,6 +84,26 @@ pub fn analyze_doc_tag_alias(analyzer: &mut DeclAnalyzer, alias: LuaDocTagAlias) Some(()) } +pub fn analyze_doc_tag_attribute( + analyzer: &mut DeclAnalyzer, + attribute: LuaDocTagAttribute, +) -> Option<()> { + let name_token = attribute.get_name_token()?; + let name = name_token.get_name_text().to_string(); + let range = name_token.syntax().text_range(); + + // LuaTypeAttribute 与 LuaDocTagAttribute 完全是两个不同的概念. + // LuaTypeAttribute 描述类型的属性, LuaDocTagAttribute 类似于 C# 的特性, 附加了更多的信息. + add_type_decl( + analyzer, + &name, + range, + LuaDeclTypeKind::Attribute, + LuaTypeAttribute::None.into(), + ); + Some(()) +} + pub fn analyze_doc_tag_namespace( analyzer: &mut DeclAnalyzer, namespace: LuaDocTagNamespace, diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs index 841770e63..859111678 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs @@ -107,6 +107,9 @@ fn walk_node_enter(analyzer: &mut DeclAnalyzer, node: LuaAst) { LuaAst::LuaDocTagAlias(doc_tag) => { docs::analyze_doc_tag_alias(analyzer, doc_tag); } + LuaAst::LuaDocTagAttribute(doc_tag) => { + docs::analyze_doc_tag_attribute(analyzer, doc_tag); + } LuaAst::LuaDocTagNamespace(doc_tag) => { docs::analyze_doc_tag_namespace(analyzer, doc_tag); } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs new file mode 100644 index 000000000..298e33814 --- /dev/null +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -0,0 +1,110 @@ +use emmylua_parser::{ + LuaAst, LuaAstNode, LuaDocTagAttributeUse, LuaExpr, LuaKind, LuaLiteralExpr, LuaLiteralToken, + LuaSyntaxKind, LuaSyntaxNode, +}; +use smol_str::SmolStr; + +use crate::{ + LuaAttributeUse, LuaType, + compilation::analyzer::doc::{ + DocAnalyzer, + tags::{get_owner_id, report_orphan_tag}, + }, +}; + +pub fn analyze_tag_attribute_use( + analyzer: &mut DocAnalyzer, + attribute_use: LuaDocTagAttributeUse, +) -> Option<()> { + let owner = attribute_use_get_owner(analyzer, &attribute_use); + let owner_id = match get_owner_id(analyzer, owner, true) { + Some(id) => id, + None => { + report_orphan_tag(analyzer, &attribute_use); + return None; + } + }; + let attribute_uses = attribute_use.get_attribute_uses(); + for attribute_use in attribute_uses { + let mut params = Vec::new(); + if let Some(attribute_call_arg_list) = attribute_use.get_attribute_call_arg_list() { + for arg in attribute_call_arg_list.get_args() { + let arg_type = infer_attribute_arg_type(arg); + params.push(arg_type); + } + } + analyzer.db.get_property_index_mut().add_attribute_use( + analyzer.file_id, + owner_id.clone(), + LuaAttributeUse::new( + attribute_use.get_name_token()?.get_name_text().to_string(), + params, + ), + ); + } + Some(()) +} + +fn infer_attribute_arg_type(expr: LuaLiteralExpr) -> LuaType { + if let Some(literal_token) = expr.get_literal() { + match literal_token { + LuaLiteralToken::String(str_token) => { + return LuaType::DocStringConst(SmolStr::new(str_token.get_value()).into()); + } + LuaLiteralToken::Number(number_token) => { + if number_token.is_int() { + return LuaType::DocIntegerConst(number_token.get_int_value()); + } else { + return LuaType::Number; + } + } + LuaLiteralToken::Bool(bool_token) => { + return LuaType::DocBooleanConst(bool_token.is_true()); + } + LuaLiteralToken::Nil(_) => return LuaType::Nil, + // todo + LuaLiteralToken::Dots(_) => return LuaType::Any, + LuaLiteralToken::Question(_) => return LuaType::Nil, + } + } + LuaType::Unknown +} + +/// 特性的寻找所有者需要特殊处理 +fn attribute_use_get_owner( + analyzer: &mut DocAnalyzer, + attribute_use: &LuaDocTagAttributeUse, +) -> Option { + // 针对 ---@field 特殊处理 + if let Some(attached_node) = attribute_find_doc_field(&attribute_use.syntax()) { + return LuaAst::cast(attached_node); + } + // 回退 + analyzer.comment.get_owner() +} + +fn attribute_find_doc_field(comment: &LuaSyntaxNode) -> Option { + let mut next_sibling = comment.next_sibling(); + loop { + next_sibling.as_ref()?; + if let Some(sibling) = &next_sibling { + match sibling.kind() { + LuaKind::Syntax(LuaSyntaxKind::DocTagField) => { + return Some(sibling.clone()); + } + LuaKind::Syntax(LuaSyntaxKind::Comment) => { + return None; + } + LuaKind::Syntax(LuaSyntaxKind::Block) => { + return None; + } + _ => { + if LuaExpr::can_cast(sibling.kind().into()) { + return None; + } + } + } + next_sibling = sibling.next_sibling(); + } + } +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/infer_type.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/infer_type.rs index 4d23843f5..7b7efbc46 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/infer_type.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/infer_type.rs @@ -1,17 +1,17 @@ use std::sync::Arc; use emmylua_parser::{ - LuaAst, LuaAstNode, LuaDocBinaryType, LuaDocDescriptionOwner, LuaDocFuncType, - LuaDocGenericType, LuaDocMultiLineUnionType, LuaDocObjectFieldKey, LuaDocObjectType, - LuaDocStrTplType, LuaDocType, LuaDocUnaryType, LuaDocVariadicType, LuaLiteralToken, - LuaSyntaxKind, LuaTypeBinaryOperator, LuaTypeUnaryOperator, LuaVarExpr, + LuaAst, LuaAstNode, LuaDocAttributeType, LuaDocBinaryType, LuaDocDescriptionOwner, + LuaDocFuncType, LuaDocGenericType, LuaDocMultiLineUnionType, LuaDocObjectFieldKey, + LuaDocObjectType, LuaDocStrTplType, LuaDocType, LuaDocUnaryType, LuaDocVariadicType, + LuaLiteralToken, LuaSyntaxKind, LuaTypeBinaryOperator, LuaTypeUnaryOperator, LuaVarExpr, }; use rowan::TextRange; use smol_str::SmolStr; use crate::{ AsyncState, DiagnosticCode, GenericTpl, InFiled, LuaAliasCallKind, LuaArrayLen, LuaArrayType, - LuaMultiLineUnion, LuaTupleStatus, LuaTypeDeclId, TypeOps, VariadicType, + LuaAttributeType, LuaMultiLineUnion, LuaTupleStatus, LuaTypeDeclId, TypeOps, VariadicType, db_index::{ AnalyzeError, LuaAliasCallType, LuaFunctionType, LuaGenericType, LuaIndexAccessKey, LuaIntersectionType, LuaObjectType, LuaStringTplType, LuaTupleType, LuaType, @@ -108,6 +108,9 @@ pub fn infer_type(analyzer: &mut DocAnalyzer, node: LuaDocType) -> LuaType { LuaDocType::MultiLineUnion(multi_union) => { return infer_multi_line_union_type(analyzer, multi_union); } + LuaDocType::Attribute(attribute_type) => { + return infer_attribute_type(analyzer, attribute_type); + } _ => {} // LuaDocType::Conditional(lua_doc_conditional_type) => todo!(), } LuaType::Unknown @@ -637,3 +640,35 @@ fn infer_multi_line_union_type( LuaType::MultiLineUnion(LuaMultiLineUnion::new(union_members).into()) } + +fn infer_attribute_type( + analyzer: &mut DocAnalyzer, + attribute_type: &LuaDocAttributeType, +) -> LuaType { + let mut params_result = Vec::new(); + for param in attribute_type.get_params() { + let name = if let Some(param) = param.get_name_token() { + param.get_name_text().to_string() + } else if param.is_dots() { + "...".to_string() + } else { + continue; + }; + + let nullable = param.is_nullable(); + + let type_ref = if let Some(type_ref) = param.get_type() { + let mut typ = infer_type(analyzer, type_ref); + if nullable && !typ.is_nullable() { + typ = TypeOps::Union.apply(analyzer.db, &typ, &LuaType::Nil); + } + Some(typ) + } else { + None + }; + + params_result.push((name, type_ref)); + } + + LuaType::DocAttribute(LuaAttributeType::new(params_result).into()) +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/mod.rs index 083790a9c..844748055 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/mod.rs @@ -1,3 +1,4 @@ +mod attribute_tags; mod diagnostic_tags; mod field_or_operator_def_tags; mod file_generic_index; @@ -48,7 +49,7 @@ fn analyze_comment(analyzer: &mut DocAnalyzer) -> Option<()> { tags::analyze_tag(analyzer, tag); } - let owenr = get_owner_id(analyzer)?; + let owenr = get_owner_id(analyzer, None, false)?; let comment_description = preprocess_description( &comment.get_description()?.get_description_text(), Some(&owenr), diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/tags.rs index 90a31f1b8..ea4719c45 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/tags.rs @@ -4,7 +4,10 @@ use emmylua_parser::{ use crate::{ AnalyzeError, DiagnosticCode, LuaDeclId, - compilation::analyzer::doc::property_tags::analyze_readonly, + compilation::analyzer::doc::{ + attribute_tags::analyze_tag_attribute_use, property_tags::analyze_readonly, + type_def_tags::analyze_attribute, + }, db_index::{LuaMemberId, LuaSemanticDeclId, LuaSignatureId}, }; @@ -38,6 +41,9 @@ pub fn analyze_tag(analyzer: &mut DocAnalyzer, tag: LuaDocTag) -> Option<()> { LuaDocTag::Alias(alias) => { analyze_alias(analyzer, alias)?; } + LuaDocTag::Attribute(attribute) => { + analyze_attribute(analyzer, attribute)?; + } // ref LuaDocTag::Type(type_tag) => { @@ -111,6 +117,10 @@ pub fn analyze_tag(analyzer: &mut DocAnalyzer, tag: LuaDocTag) -> Option<()> { LuaDocTag::Readonly(readonly) => { analyze_readonly(analyzer, readonly)?; } + // 属性使用, 与 ---@tag 的语法不同 + LuaDocTag::AttributeUse(attribute_use) => { + analyze_tag_attribute_use(analyzer, attribute_use)?; + } _ => {} } @@ -152,11 +162,17 @@ pub fn find_owner_closure_or_report( } } -pub fn get_owner_id(analyzer: &mut DocAnalyzer) -> Option { - if let Some(current_type_id) = &analyzer.current_type_id { - return Some(LuaSemanticDeclId::TypeDecl(current_type_id.clone())); +pub fn get_owner_id( + analyzer: &mut DocAnalyzer, + owner: Option, + find_doc_field: bool, +) -> Option { + if !find_doc_field { + if let Some(current_type_id) = &analyzer.current_type_id { + return Some(LuaSemanticDeclId::TypeDecl(current_type_id.clone())); + } } - let owner = analyzer.comment.get_owner()?; + let owner = owner.or_else(|| analyzer.comment.get_owner())?; match owner { LuaAst::LuaAssignStat(assign) => { let first_var = assign.child::()?; @@ -197,6 +213,10 @@ pub fn get_owner_id(analyzer: &mut DocAnalyzer) -> Option { LuaAst::LuaClosureExpr(closure) => Some(LuaSemanticDeclId::Signature( LuaSignatureId::from_closure(analyzer.file_id, &closure), )), + LuaAst::LuaDocTagField(tag) => { + let member_id = LuaMemberId::new(tag.get_syntax_id(), analyzer.file_id); + Some(LuaSemanticDeclId::Member(member_id)) + } _ => { let closure = find_owner_closure(analyzer)?; Some(LuaSemanticDeclId::Signature(LuaSignatureId::from_closure( @@ -211,7 +231,7 @@ pub fn get_owner_id_or_report( analyzer: &mut DocAnalyzer, tag: &impl LuaAstNode, ) -> Option { - match get_owner_id(analyzer) { + match get_owner_id(analyzer, None, false) { Some(id) => Some(id), None => { report_orphan_tag(analyzer, tag); diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs index 504364c64..55a2091ec 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs @@ -1,8 +1,8 @@ use emmylua_parser::{ LuaAssignStat, LuaAst, LuaAstNode, LuaAstToken, LuaCommentOwner, LuaDocDescription, - LuaDocDescriptionOwner, LuaDocGenericDeclList, LuaDocTagAlias, LuaDocTagClass, LuaDocTagEnum, - LuaDocTagGeneric, LuaFuncStat, LuaLocalName, LuaLocalStat, LuaNameExpr, LuaSyntaxId, - LuaSyntaxKind, LuaTokenKind, LuaVarExpr, + LuaDocDescriptionOwner, LuaDocGenericDeclList, LuaDocTagAlias, LuaDocTagAttribute, + LuaDocTagClass, LuaDocTagEnum, LuaDocTagGeneric, LuaFuncStat, LuaLocalName, LuaLocalStat, + LuaNameExpr, LuaSyntaxId, LuaSyntaxKind, LuaTokenKind, LuaVarExpr, }; use rowan::TextRange; use smol_str::SmolStr; @@ -174,6 +174,32 @@ pub fn analyze_alias(analyzer: &mut DocAnalyzer, tag: LuaDocTagAlias) -> Option< Some(()) } +/// 分析属性定义 +pub fn analyze_attribute(analyzer: &mut DocAnalyzer, tag: LuaDocTagAttribute) -> Option<()> { + let file_id = analyzer.file_id; + let name = tag.get_name_token()?.get_name_text().to_string(); + + let decl_id = { + let decl = analyzer + .db + .get_type_index() + .find_type_decl(file_id, &name)?; + if !decl.is_attribute() { + return None; + } + decl.get_id() + }; + let attribute_type = infer_type(analyzer, tag.get_type()?); + let attribute_decl = analyzer + .db + .get_type_index_mut() + .get_type_decl_mut(&decl_id)?; + attribute_decl.add_attribute_type(attribute_type); + + add_description_for_type_decl(analyzer, &decl_id, tag.get_descriptions()); + Some(()) +} + fn get_generic_params( analyzer: &mut DocAnalyzer, params: LuaDocGenericDeclList, diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs index 8b4e15393..44775adfd 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs @@ -247,7 +247,7 @@ pub fn analyze_return(analyzer: &mut DocAnalyzer, tag: LuaDocTagReturn) -> Optio } pub fn analyze_return_cast(analyzer: &mut DocAnalyzer, tag: LuaDocTagReturnCast) -> Option<()> { - if let Some(LuaSemanticDeclId::Signature(signature_id)) = get_owner_id(analyzer) { + if let Some(LuaSemanticDeclId::Signature(signature_id)) = get_owner_id(analyzer, None, false) { let name_token = tag.get_name_token()?; let name = name_token.get_name_text(); let cast_op_type = tag.get_op_type()?; @@ -396,7 +396,7 @@ pub fn analyze_see(analyzer: &mut DocAnalyzer, tag: LuaDocTagSee) -> Option<()> } pub fn analyze_other(analyzer: &mut DocAnalyzer, other: LuaDocTagOther) -> Option<()> { - let owner = get_owner_id(analyzer)?; + let owner = get_owner_id(analyzer, None, false)?; let tag_name = other.get_tag_name()?; let description = if let Some(des) = other.get_description() { preprocess_description(&des.get_description_text(), None) diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs new file mode 100644 index 000000000..0b043bd15 --- /dev/null +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -0,0 +1,41 @@ +#[cfg(test)] +mod test { + use crate::VirtualWorkspace; + + #[test] + fn test_def_attribute() { + let mut ws = VirtualWorkspace::new(); + + // ws.def( + // r#" + // ---@attribute check_point(x: string, y: number) + // "#, + // ); + + // ws.def( + // r#" + // ---@attribute SkipDiagnosticTable() + + // ---@[SkipDiagnosticTable, Skip] + // local config = {} + // "#, + // ); + + ws.def( + r#" + ---@class A + ---@field a string + ---@[deprecated] + ---@field b string + "#, + ); + + // ws.def( + // r#" + + // ---@[deprecated] + // local a + // "#, + // ); + } +} diff --git a/crates/emmylua_code_analysis/src/compilation/test/mod.rs b/crates/emmylua_code_analysis/src/compilation/test/mod.rs index 58d8faec8..188a8cbb6 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/mod.rs @@ -1,6 +1,7 @@ mod and_or_test; mod annotation_test; mod array_test; +mod attribute_test; mod closure_generic; mod closure_param_infer_test; mod closure_return_test; diff --git a/crates/emmylua_code_analysis/src/db_index/property/mod.rs b/crates/emmylua_code_analysis/src/db_index/property/mod.rs index 02e8751b5..85eea4f93 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/mod.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/mod.rs @@ -9,6 +9,7 @@ use emmylua_parser::{LuaAstNode, LuaDocTagField, LuaDocType, LuaVersionCondition pub use property::LuaCommonProperty; pub use property::{LuaDeprecated, LuaExport, LuaExportScope, LuaPropertyId}; +pub use crate::db_index::property::property::LuaAttributeUse; use crate::{DbIndex, FileId, LuaMember, LuaSignatureId}; use super::{LuaSemanticDeclId, traits::LuaIndex}; @@ -234,6 +235,22 @@ impl LuaPropertyIndex { Some(()) } + pub fn add_attribute_use( + &mut self, + file_id: FileId, + owner_id: LuaSemanticDeclId, + attribute_use: LuaAttributeUse, + ) -> Option<()> { + let (property, _) = self.get_or_create_property(owner_id.clone())?; + property.add_attribute_use(attribute_use); + + self.in_filed_owner + .entry(file_id) + .or_default() + .insert(owner_id); + Some(()) + } + pub fn get_property(&self, owner_id: &LuaSemanticDeclId) -> Option<&LuaCommonProperty> { self.property_owners_map .get(owner_id) diff --git a/crates/emmylua_code_analysis/src/db_index/property/property.rs b/crates/emmylua_code_analysis/src/db_index/property/property.rs index 5489334f8..1bbdd3d46 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/property.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/property.rs @@ -1,6 +1,11 @@ +use std::sync::Arc; + use emmylua_parser::{LuaVersionCondition, VisibilityKind}; -use crate::db_index::property::decl_feature::{DeclFeatureFlag, PropertyDeclFeature}; +use crate::{ + LuaType, + db_index::property::decl_feature::{DeclFeatureFlag, PropertyDeclFeature}, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct LuaCommonProperty { @@ -12,6 +17,7 @@ pub struct LuaCommonProperty { pub tag_content: Option>, pub export: Option, pub decl_features: DeclFeatureFlag, + pub attribute_uses: Option>>, } impl Default for LuaCommonProperty { @@ -31,6 +37,7 @@ impl LuaCommonProperty { tag_content: None, export: None, decl_features: DeclFeatureFlag::new(), + attribute_uses: None, } } @@ -90,6 +97,14 @@ impl LuaCommonProperty { pub fn add_decl_feature(&mut self, feature: PropertyDeclFeature) { self.decl_features.add_feature(feature); } + + pub fn add_attribute_use(&mut self, attribute_use: LuaAttributeUse) { + Arc::make_mut( + self.attribute_uses + .get_or_insert_with(|| Arc::new(Vec::new())), + ) + .push(attribute_use); + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -144,3 +159,15 @@ impl LuaPropertyId { Self { id } } } + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct LuaAttributeUse { + pub name: String, + pub params: Vec, +} + +impl LuaAttributeUse { + pub fn new(name: String, params: Vec) -> Self { + Self { name, params } + } +} diff --git a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs index 0c1d2323d..ff4e35c3b 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs @@ -15,6 +15,7 @@ pub enum LuaDeclTypeKind { Class, Enum, Alias, + Attribute, } flags! { @@ -57,6 +58,7 @@ impl LuaTypeDecl { LuaDeclTypeKind::Enum => LuaTypeExtra::Enum { base: None }, LuaDeclTypeKind::Class => LuaTypeExtra::Class, LuaDeclTypeKind::Alias => LuaTypeExtra::Alias { origin: None }, + LuaDeclTypeKind::Attribute => LuaTypeExtra::Attribute { typ: None }, }, } } @@ -85,6 +87,10 @@ impl LuaTypeDecl { matches!(self.extra, LuaTypeExtra::Alias { .. }) } + pub fn is_attribute(&self) -> bool { + matches!(self.extra, LuaTypeExtra::Attribute { .. }) + } + pub fn is_exact(&self) -> bool { self.locations .iter() @@ -166,6 +172,12 @@ impl LuaTypeDecl { } } + pub fn add_attribute_type(&mut self, attribute_type: LuaType) { + if let LuaTypeExtra::Attribute { typ } = &mut self.extra { + *typ = Some(attribute_type); + } + } + pub fn merge_decl(&mut self, other: LuaTypeDecl) { self.locations.extend(other.locations); } @@ -309,4 +321,5 @@ pub enum LuaTypeExtra { Enum { base: Option }, Class, Alias { origin: Option }, + Attribute { typ: Option }, } diff --git a/crates/emmylua_code_analysis/src/db_index/type/types.rs b/crates/emmylua_code_analysis/src/db_index/type/types.rs index 38892504c..116b35021 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/types.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/types.rs @@ -63,6 +63,7 @@ pub enum LuaType { ConstTplRef(Arc), Language(ArcIntern), ModuleRef(FileId), + DocAttribute(Arc), } impl PartialEq for LuaType { @@ -113,6 +114,7 @@ impl PartialEq for LuaType { (LuaType::ConstTplRef(a), LuaType::ConstTplRef(b)) => a == b, (LuaType::Language(a), LuaType::Language(b)) => a == b, (LuaType::ModuleRef(a), LuaType::ModuleRef(b)) => a == b, + (LuaType::DocAttribute(a), LuaType::DocAttribute(b)) => a == b, _ => false, // 不同变体之间不相等 } } @@ -192,6 +194,7 @@ impl Hash for LuaType { LuaType::ConstTplRef(a) => (46, a).hash(state), LuaType::Language(a) => (47, a).hash(state), LuaType::ModuleRef(a) => (48, a).hash(state), + LuaType::DocAttribute(a) => (52, a).hash(state), } } } @@ -1451,3 +1454,31 @@ impl LuaArrayType { self.base.contain_tpl() } } + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct LuaAttributeType { + params: Vec<(String, Option)>, +} + +impl TypeVisitTrait for LuaAttributeType { + fn visit_type(&self, f: &mut F) + where + F: FnMut(&LuaType), + { + for (_, t) in &self.params { + if let Some(t) = t { + t.visit_type(f); + } + } + } +} + +impl LuaAttributeType { + pub fn new(params: Vec<(String, Option)>) -> Self { + Self { params } + } + + pub fn get_params(&self) -> &[(String, Option)] { + &self.params + } +} diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 17fbbe5ad..2ae7a214d 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -58,7 +58,7 @@ fn parse_tag_detail(p: &mut LuaDocParser) -> DocParseResult { LuaTokenKind::TkTagExport => parse_tag_export(p), LuaTokenKind::TkLanguage => parse_tag_language(p), LuaTokenKind::TkTagAttribute => parse_tag_attribute(p), - LuaTokenKind::TkDocAttribute => parse_attribute_usage(p), + LuaTokenKind::TkDocAttribute => parse_attribute_use(p), // simple tag LuaTokenKind::TkTagVisibility => parse_tag_simple(p, LuaSyntaxKind::DocTagVisibility), @@ -683,15 +683,16 @@ fn parse_type_attribute(p: &mut LuaDocParser) -> DocParseResult { } // ---@[attribute_name(params)] or ---@[attribute_name] -pub fn parse_attribute_usage(p: &mut LuaDocParser) -> DocParseResult { - let m = p.mark(LuaSyntaxKind::DocAttributeUsage); +pub fn parse_attribute_use(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocTagAttributeUse); p.bump(); // consume '[' - expect_token(p, LuaTokenKind::TkName)?; - - // 解析参数列表, 允许没有参数的特性在使用时省略括号 - if p.current_token() == LuaTokenKind::TkLeftParen { - parse_attribute_arg_list(p)?; + while p.current_token() == LuaTokenKind::TkName { + parse_doc_attribute_use(p)?; + if p.current_token() != LuaTokenKind::TkComma { + break; + } + p.bump(); // consume comma } // 期望结束符号 ']' @@ -703,9 +704,23 @@ pub fn parse_attribute_usage(p: &mut LuaDocParser) -> DocParseResult { Ok(m.complete(p)) } +// ---@[attribute1, attribute2, ...] +fn parse_doc_attribute_use(p: &mut LuaDocParser) -> DocParseResult { + let m = p.mark(LuaSyntaxKind::DocAttributeUse); + + expect_token(p, LuaTokenKind::TkName)?; + + // 解析参数列表, 允许没有参数的特性在使用时省略括号 + if p.current_token() == LuaTokenKind::TkLeftParen { + parse_attribute_arg_list(p)?; + } + + Ok(m.complete(p)) +} + // 解析属性参数列表 fn parse_attribute_arg_list(p: &mut LuaDocParser) -> DocParseResult { - let m = p.mark(LuaSyntaxKind::DocAttributeArgList); + let m = p.mark(LuaSyntaxKind::DocAttributeCallArgList); p.bump(); // consume '(' // 解析参数值列表 @@ -731,30 +746,28 @@ fn parse_attribute_arg_list(p: &mut LuaDocParser) -> DocParseResult { // 解析单个属性参数 fn parse_attribute_arg(p: &mut LuaDocParser) -> DocParseResult { - let m = p.mark(LuaSyntaxKind::DocAttributeArg); + let m = p.mark(LuaSyntaxKind::LiteralExpr); // TODO: 添加具名参数支持(name = value) - parse_attribute_arg_value(p)?; - - Ok(m.complete(p)) -} - -// 解析属性参数值 -fn parse_attribute_arg_value(p: &mut LuaDocParser) -> Result<(), LuaParseError> { match p.current_token() { - LuaTokenKind::TkString - | LuaTokenKind::TkLongString - | LuaTokenKind::TkInt + LuaTokenKind::TkInt | LuaTokenKind::TkFloat + | LuaTokenKind::TkComplex + | LuaTokenKind::TkNil | LuaTokenKind::TkTrue | LuaTokenKind::TkFalse - | LuaTokenKind::TkName => { + | LuaTokenKind::TkDots + | LuaTokenKind::TkString + | LuaTokenKind::TkLongString => { p.bump(); - Ok(()) } - _ => Err(LuaParseError::doc_error_from( - "Expected attribute argument value", - p.current_token_range(), - )), - } + _ => { + return Err(LuaParseError::doc_error_from( + "Expected attribute argument value", + p.current_token_range(), + )); + } + }; + + Ok(m.complete(p)) } diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 9488c3e47..427f629f8 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2872,7 +2872,7 @@ Syntax(Chunk)@0..137 fn test_attribute_doc() { let code = r#" ---@attribute check_point(x: string, y: number) - ---@[check_point("a", 0)] + ---@[Skip, check_point("a", 0)] "#; // print_ast(code); // print_ast(r#" @@ -2880,11 +2880,11 @@ Syntax(Chunk)@0..137 // check_point("a", 0) // "#); let result = r#" -Syntax(Chunk)@0..99 - Syntax(Block)@0..99 +Syntax(Chunk)@0..105 + Syntax(Block)@0..105 Token(TkEndOfLine)@0..1 "\n" Token(TkWhitespace)@1..9 " " - Syntax(Comment)@9..90 + Syntax(Comment)@9..96 Token(TkDocStart)@9..13 "---@" Syntax(DocTagAttribute)@13..56 Token(TkTagAttribute)@13..22 "attribute" @@ -2910,21 +2910,26 @@ Syntax(Chunk)@0..99 Token(TkEndOfLine)@56..57 "\n" Token(TkWhitespace)@57..65 " " Token(TkDocStart)@65..69 "---@" - Syntax(DocAttributeUsage)@69..90 + Syntax(DocTagAttributeUse)@69..96 Token(TkDocAttribute)@69..70 "[" - Token(TkName)@70..81 "check_point" - Syntax(DocAttributeArgList)@81..89 - Token(TkLeftParen)@81..82 "(" - Syntax(DocAttributeArg)@82..85 - Token(TkString)@82..85 "\"a\"" - Token(TkComma)@85..86 "," - Token(TkWhitespace)@86..87 " " - Syntax(DocAttributeArg)@87..88 - Token(TkInt)@87..88 "0" - Token(TkRightParen)@88..89 ")" - Token(TkRightBracket)@89..90 "]" - Token(TkEndOfLine)@90..91 "\n" - Token(TkWhitespace)@91..99 " " + Syntax(DocAttributeUse)@70..74 + Token(TkName)@70..74 "Skip" + Token(TkComma)@74..75 "," + Token(TkWhitespace)@75..76 " " + Syntax(DocAttributeUse)@76..95 + Token(TkName)@76..87 "check_point" + Syntax(DocAttributeCallArgList)@87..95 + Token(TkLeftParen)@87..88 "(" + Syntax(LiteralExpr)@88..91 + Token(TkString)@88..91 "\"a\"" + Token(TkComma)@91..92 "," + Token(TkWhitespace)@92..93 " " + Syntax(LiteralExpr)@93..94 + Token(TkInt)@93..94 "0" + Token(TkRightParen)@94..95 ")" + Token(TkRightBracket)@95..96 "]" + Token(TkEndOfLine)@96..97 "\n" + Token(TkWhitespace)@97..105 " " "#; assert_ast_eq!(code, result); } diff --git a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs index 8d6a512bc..95ffa6628 100644 --- a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs @@ -93,6 +93,7 @@ pub enum LuaSyntaxKind { DocTagExport, DocTagLanguage, DocTagAttribute, + DocTagAttributeUse, // '@[' // doc Type TypeArray, // baseType [] @@ -109,7 +110,7 @@ pub enum LuaSyntaxKind { TypeNullable, // ? TypeStringTemplate, // prefixName.`T` TypeMultiLineUnion, // | simple type # description - TypeAttribute, // attribute(paramList) + TypeAttribute, // declare. attribute<(paramList)> // follow donot support now TypeMatch, @@ -126,13 +127,12 @@ pub enum LuaSyntaxKind { DocGenericDeclareList, DocDiagnosticNameList, DocTypeList, - DocAttribute, // (partial, global, local, ...) - DocAttributeUsage, // '@[attribute_name(params)]' or '@[attribute_name]' - DocAttributeArgList, // argument list in @[attribute_name(arg1, arg2, ...)] - DocAttributeArg, // single argument in attribute usage - DocOpType, // +, -, +? - DocMappedKeys, // [p in KeyType]? - DocEnumFieldList, // ---| + DocAttribute, // (partial, global, local, ...) + DocAttributeUse, // use. attribute in @[attribute1, attribute2, ...] + DocAttributeCallArgList, // use. argument list in @[attribute_name(arg1, arg2, ...)] + DocOpType, // +, -, +? + DocMappedKeys, // [p in KeyType]? + DocEnumFieldList, // ---| DocEnumField, // # description or # description or # description DocOneLineField, // # description DocDiagnosticCodeList, // unused-local, undefined-global ... diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index cc34d9393..d913d47b9 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -27,7 +27,7 @@ pub enum LuaDocLexerState { Source, NormalDescription, CastExpr, - AttributeUsage, + AttributeUse, } impl LuaDocLexer<'_> { @@ -74,7 +74,7 @@ impl LuaDocLexer<'_> { LuaDocLexerState::Source => self.lex_source(), LuaDocLexerState::NormalDescription => self.lex_normal_description(), LuaDocLexerState::CastExpr => self.lex_cast_expr(), - LuaDocLexerState::AttributeUsage => self.lex_attribute_usage(), + LuaDocLexerState::AttributeUse => self.lex_attribute_use(), } } @@ -146,7 +146,7 @@ impl LuaDocLexer<'_> { } '[' => { reader.bump(); - self.state = LuaDocLexerState::AttributeUsage; + self.state = LuaDocLexerState::AttributeUse; LuaTokenKind::TkDocAttribute } ch if is_name_start(ch) => { @@ -600,7 +600,7 @@ impl LuaDocLexer<'_> { } } - fn lex_attribute_usage(&mut self) -> LuaTokenKind { + fn lex_attribute_use(&mut self) -> LuaTokenKind { let reader = self.reader.as_mut().unwrap(); match reader.current_char() { ch if is_doc_whitespace(ch) => { diff --git a/crates/emmylua_parser/src/parser/lua_doc_parser.rs b/crates/emmylua_parser/src/parser/lua_doc_parser.rs index 287cf6839..02457ec94 100644 --- a/crates/emmylua_parser/src/parser/lua_doc_parser.rs +++ b/crates/emmylua_parser/src/parser/lua_doc_parser.rs @@ -94,7 +94,7 @@ impl<'b> LuaDocParser<'_, 'b> { LuaDocLexerState::FieldStart | LuaDocLexerState::See | LuaDocLexerState::Source - | LuaDocLexerState::AttributeUsage => { + | LuaDocLexerState::AttributeUse => { while matches!(self.current_token, LuaTokenKind::TkWhitespace) { self.eat_current_and_lex_next(); } diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index 2a247e211..2274b4c2c 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -11,7 +11,7 @@ use super::{ LuaAst, LuaBinaryOpToken, LuaLiteralToken, LuaNameToken, LuaNumberToken, LuaStringToken, }; use crate::{ - LuaAstChildren, LuaAstToken, LuaAstTokenChildren, LuaKind, LuaSyntaxNode, + LuaAstChildren, LuaAstToken, LuaAstTokenChildren, LuaKind, LuaLiteralExpr, LuaSyntaxNode, kind::{LuaSyntaxKind, LuaTokenKind}, syntax::traits::LuaAstNode, }; @@ -487,3 +487,77 @@ impl LuaDocNamedReturnType { } } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LuaDocAttributeUse { + syntax: LuaSyntaxNode, +} + +impl LuaAstNode for LuaDocAttributeUse { + fn syntax(&self) -> &LuaSyntaxNode { + &self.syntax + } + + fn can_cast(kind: LuaSyntaxKind) -> bool + where + Self: Sized, + { + kind == LuaSyntaxKind::DocAttributeUse + } + + fn cast(syntax: LuaSyntaxNode) -> Option + where + Self: Sized, + { + if Self::can_cast(syntax.kind().into()) { + Some(Self { syntax }) + } else { + None + } + } +} + +impl LuaDocAttributeUse { + pub fn get_name_token(&self) -> Option { + self.token() + } + + pub fn get_attribute_call_arg_list(&self) -> Option { + self.child() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LuaDocAttributeCallArgList { + syntax: LuaSyntaxNode, +} + +impl LuaAstNode for LuaDocAttributeCallArgList { + fn syntax(&self) -> &LuaSyntaxNode { + &self.syntax + } + + fn can_cast(kind: LuaSyntaxKind) -> bool + where + Self: Sized, + { + kind == LuaSyntaxKind::DocAttributeCallArgList + } + + fn cast(syntax: LuaSyntaxNode) -> Option + where + Self: Sized, + { + if Self::can_cast(syntax.kind().into()) { + Some(Self { syntax }) + } else { + None + } + } +} + +impl LuaDocAttributeCallArgList { + pub fn get_args(&self) -> LuaAstChildren { + self.children() + } +} diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index fc557e9d7..4dfa3302b 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -1,8 +1,8 @@ use crate::{ BinaryOperator, LuaAstChildren, LuaAstToken, LuaAstTokenChildren, LuaBinaryOpToken, - LuaDocVersionNumberToken, LuaDocVisibilityToken, LuaExpr, LuaGeneralToken, LuaKind, - LuaNameToken, LuaNumberToken, LuaPathToken, LuaStringToken, LuaSyntaxNode, LuaTokenKind, - LuaVersionCondition, + LuaDocAttributeUse, LuaDocVersionNumberToken, LuaDocVisibilityToken, LuaExpr, LuaGeneralToken, + LuaKind, LuaNameToken, LuaNumberToken, LuaPathToken, LuaStringToken, LuaSyntaxNode, + LuaTokenKind, LuaVersionCondition, kind::LuaSyntaxKind, syntax::{LuaDocDescriptionOwner, traits::LuaAstNode}, }; @@ -18,6 +18,7 @@ pub enum LuaDocTag { Enum(LuaDocTagEnum), Alias(LuaDocTagAlias), Attribute(LuaDocTagAttribute), + AttributeUse(LuaDocTagAttributeUse), Type(LuaDocTagType), Param(LuaDocTagParam), Return(LuaDocTagReturn), @@ -79,6 +80,7 @@ impl LuaAstNode for LuaDocTag { LuaDocTag::ReturnCast(it) => it.syntax(), LuaDocTag::Export(it) => it.syntax(), LuaDocTag::Language(it) => it.syntax(), + LuaDocTag::AttributeUse(it) => it.syntax(), } } @@ -116,6 +118,7 @@ impl LuaAstNode for LuaDocTag { || kind == LuaSyntaxKind::DocTagReturnCast || kind == LuaSyntaxKind::DocTagExport || kind == LuaSyntaxKind::DocTagLanguage + || kind == LuaSyntaxKind::DocTagAttributeUse } fn cast(syntax: LuaSyntaxNode) -> Option @@ -135,6 +138,9 @@ impl LuaAstNode for LuaDocTag { LuaSyntaxKind::DocTagAttribute => Some(LuaDocTag::Attribute( LuaDocTagAttribute::cast(syntax).unwrap(), )), + LuaSyntaxKind::DocTagAttributeUse => Some(LuaDocTag::AttributeUse( + LuaDocTagAttributeUse::cast(syntax).unwrap(), + )), LuaSyntaxKind::DocTagType => { Some(LuaDocTag::Type(LuaDocTagType::cast(syntax).unwrap())) } @@ -1575,6 +1581,8 @@ impl LuaAstNode for LuaDocTagAttribute { } } +impl LuaDocDescriptionOwner for LuaDocTagAttribute {} + impl LuaDocTagAttribute { pub fn get_name_token(&self) -> Option { self.token() @@ -1584,3 +1592,37 @@ impl LuaDocTagAttribute { self.child() } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LuaDocTagAttributeUse { + syntax: LuaSyntaxNode, +} + +impl LuaAstNode for LuaDocTagAttributeUse { + fn syntax(&self) -> &LuaSyntaxNode { + &self.syntax + } + + fn can_cast(kind: LuaSyntaxKind) -> bool { + kind == LuaSyntaxKind::DocTagAttributeUse + } + + fn cast(syntax: LuaSyntaxNode) -> Option { + if Self::can_cast(syntax.kind().into()) { + Some(Self { syntax }) + } else { + None + } + } +} + +impl LuaDocTagAttributeUse { + pub fn get_name_token(&self) -> Option { + self.token() + } + + /// 获取所有使用的属性 + pub fn get_attribute_uses(&self) -> LuaAstChildren { + self.children() + } +} diff --git a/crates/emmylua_parser/src/syntax/node/doc/types.rs b/crates/emmylua_parser/src/syntax/node/doc/types.rs index 2860baea2..85479c92d 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/types.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/types.rs @@ -22,6 +22,7 @@ pub enum LuaDocType { Generic(LuaDocGenericType), StrTpl(LuaDocStrTplType), MultiLineUnion(LuaDocMultiLineUnionType), + Attribute(LuaDocAttributeType), } impl LuaAstNode for LuaDocType { @@ -41,6 +42,7 @@ impl LuaAstNode for LuaDocType { LuaDocType::Generic(it) => it.syntax(), LuaDocType::StrTpl(it) => it.syntax(), LuaDocType::MultiLineUnion(it) => it.syntax(), + LuaDocType::Attribute(it) => it.syntax(), } } @@ -64,6 +66,7 @@ impl LuaAstNode for LuaDocType { | LuaSyntaxKind::TypeGeneric | LuaSyntaxKind::TypeStringTemplate | LuaSyntaxKind::TypeMultiLineUnion + | LuaSyntaxKind::TypeAttribute ) } @@ -100,6 +103,9 @@ impl LuaAstNode for LuaDocType { LuaSyntaxKind::TypeMultiLineUnion => Some(LuaDocType::MultiLineUnion( LuaDocMultiLineUnionType::cast(syntax)?, )), + LuaSyntaxKind::TypeAttribute => { + Some(LuaDocType::Attribute(LuaDocAttributeType::cast(syntax)?)) + } _ => None, } } @@ -732,3 +738,38 @@ impl LuaDocOneLineField { self.child() } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LuaDocAttributeType { + syntax: LuaSyntaxNode, +} + +impl LuaAstNode for LuaDocAttributeType { + fn syntax(&self) -> &LuaSyntaxNode { + &self.syntax + } + + fn can_cast(kind: LuaSyntaxKind) -> bool + where + Self: Sized, + { + kind == LuaSyntaxKind::TypeAttribute + } + + fn cast(syntax: LuaSyntaxNode) -> Option + where + Self: Sized, + { + if Self::can_cast(syntax.kind().into()) { + Some(Self { syntax }) + } else { + None + } + } +} + +impl LuaDocAttributeType { + pub fn get_params(&self) -> LuaAstChildren { + self.children() + } +} From dc2fae2e8a62e5def98c2d8c1aeb240a0749ea33 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 24 Sep 2025 20:14:55 +0800 Subject: [PATCH 04/30] Add built-in feature impl: deprecated, skip_diagnostic --- .../resources/std/builtin.lua | 16 +++++ .../analyzer/doc/attribute_tags.rs | 21 +++--- .../src/compilation/test/attribute_test.rs | 9 ++- .../src/db_index/property/property.rs | 12 ++-- .../checker/assign_type_mismatch.rs | 43 ++++++++---- .../src/diagnostic/checker/deprecated.rs | 70 +++++++++++-------- .../diagnostic/checker/param_type_check.rs | 2 +- .../checker/return_type_mismatch.rs | 4 +- crates/emmylua_parser/src/grammar/doc/tag.rs | 2 +- crates/emmylua_parser/src/grammar/doc/test.rs | 6 +- .../emmylua_parser/src/syntax/node/doc/mod.rs | 4 +- 11 files changed, 122 insertions(+), 67 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index 01ca3b5d5..c40c4d437 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -138,3 +138,19 @@ ---@alias TypeGuard boolean ---@alias Language string + +--- attribute + +--- Deprecated. Receives an optional message parameter. +---@attribute deprecated(message: string?) + +--- Skip partial diagnostics, typically used to optimize diagnostic performance. +--- +--- Receives a parameter, the options are: +--- - `table_field` - Skip diagnostic for `table` fields +---@attribute skip_diagnostic(code: string) + +--- Index field alias, will be displayed in `hint` and `completion`. +--- +--- Receives a string parameter for the alias name. +---@attribute index_alias(name: string) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs index 298e33814..452d70705 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -1,6 +1,6 @@ use emmylua_parser::{ - LuaAst, LuaAstNode, LuaDocTagAttributeUse, LuaExpr, LuaKind, LuaLiteralExpr, LuaLiteralToken, - LuaSyntaxKind, LuaSyntaxNode, + LuaAst, LuaAstNode, LuaDocTagAttributeUse, LuaDocType, LuaExpr, LuaKind, LuaLiteralExpr, + LuaLiteralToken, LuaSyntaxKind, LuaSyntaxNode, }; use smol_str::SmolStr; @@ -8,6 +8,7 @@ use crate::{ LuaAttributeUse, LuaType, compilation::analyzer::doc::{ DocAnalyzer, + infer_type::infer_type, tags::{get_owner_id, report_orphan_tag}, }, }; @@ -33,14 +34,14 @@ pub fn analyze_tag_attribute_use( params.push(arg_type); } } - analyzer.db.get_property_index_mut().add_attribute_use( - analyzer.file_id, - owner_id.clone(), - LuaAttributeUse::new( - attribute_use.get_name_token()?.get_name_text().to_string(), - params, - ), - ); + let attribute_type = infer_type(analyzer, LuaDocType::Name(attribute_use.get_type()?)); + if let LuaType::Ref(type_id) = attribute_type { + analyzer.db.get_property_index_mut().add_attribute_use( + analyzer.file_id, + owner_id.clone(), + LuaAttributeUse::new(type_id, params), + ); + } } Some(()) } diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index 0b043bd15..cc5478974 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -5,6 +5,13 @@ mod test { #[test] fn test_def_attribute() { let mut ws = VirtualWorkspace::new(); + ws.def( + r#" + ---@attribute Deprecated(message: string?) + ---@attribute SkipDiagnosticTable() -- 跳过对表的部分诊断, 用于优化性能, 通常来说对巨型配置表使用. + ---@attribute IndexFieldAlias(name: string) -- 索引字段别名, 将在`hint`与`completion`中显示别名. + "#, + ); // ws.def( // r#" @@ -25,7 +32,7 @@ mod test { r#" ---@class A ---@field a string - ---@[deprecated] + ---@[Deprecated] ---@field b string "#, ); diff --git a/crates/emmylua_code_analysis/src/db_index/property/property.rs b/crates/emmylua_code_analysis/src/db_index/property/property.rs index 1bbdd3d46..96f8d7208 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/property.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/property.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use emmylua_parser::{LuaVersionCondition, VisibilityKind}; use crate::{ - LuaType, + LuaType, LuaTypeDeclId, db_index::property::decl_feature::{DeclFeatureFlag, PropertyDeclFeature}, }; @@ -105,6 +105,10 @@ impl LuaCommonProperty { ) .push(attribute_use); } + + pub fn attribute_uses(&self) -> Option<&Arc>> { + self.attribute_uses.as_ref() + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -162,12 +166,12 @@ impl LuaPropertyId { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct LuaAttributeUse { - pub name: String, + pub id: LuaTypeDeclId, pub params: Vec, } impl LuaAttributeUse { - pub fn new(name: String, params: Vec) -> Self { - Self { name, params } + pub fn new(name: LuaTypeDeclId, params: Vec) -> Self { + Self { id: name, params } } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs index f8d888874..56b159115 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs @@ -2,9 +2,9 @@ use std::ops::Deref; use emmylua_parser::{ LuaAssignStat, LuaAst, LuaAstNode, LuaAstToken, LuaExpr, LuaIndexExpr, LuaLocalStat, - LuaNameExpr, LuaTableExpr, LuaVarExpr, + LuaNameExpr, LuaSyntaxNode, LuaSyntaxToken, LuaTableExpr, LuaVarExpr, }; -use rowan::TextRange; +use rowan::{NodeOrToken, TextRange}; use crate::{ DiagnosticCode, LuaDeclExtra, LuaDeclId, LuaMemberKey, LuaSemanticDeclId, LuaType, @@ -78,7 +78,7 @@ fn check_name_expr( rowan::NodeOrToken::Node(name_expr.syntax().clone()), SemanticDeclLevel::default(), )?; - let source_type = match semantic_decl { + let source_type = match semantic_decl.clone() { LuaSemanticDeclId::LuaDecl(decl_id) => { let decl = semantic_model .get_db() @@ -116,9 +116,9 @@ fn check_name_expr( check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(name_expr.syntax().clone()), &expr, source_type.as_ref(), - Some(&value_type), ); } @@ -152,9 +152,9 @@ fn check_index_expr( check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(index_expr.syntax().clone()), &expr, source_type.as_ref(), - Some(&value_type), ); } Some(()) @@ -195,9 +195,9 @@ fn check_local_stat( check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(var.syntax().clone()), expr, Some(&var_type), - Some(&value_type), ); } } @@ -207,19 +207,32 @@ fn check_local_stat( pub fn check_table_expr( context: &mut DiagnosticContext, semantic_model: &SemanticModel, + decl_node: NodeOrToken, table_expr: &LuaExpr, table_type: Option<&LuaType>, // 记录的类型 - _expr_type: Option<&LuaType>, // 实际表达式推导出的类型 ) -> Option { - // 需要进行一些过滤 - // if table_type == expr_type { - // return Some(false); - // } + // 检查是否附加了元数据以跳过诊断 + if let Some(semantic_decl) = semantic_model.find_decl(decl_node, SemanticDeclLevel::default()) { + if let Some(property) = semantic_model + .get_db() + .get_property_index() + .get_property(&semantic_decl) + { + if let Some(attribute_uses) = property.attribute_uses() { + for attribute_use in attribute_uses.iter() { + if attribute_use.id.get_name() == "skip_diagnostic" { + if let Some(LuaType::DocStringConst(code)) = attribute_use.params.get(0) { + if code.as_ref() == "table_field" { + return Some(false); + } + }; + } + } + } + } + } + let table_type = table_type?; - // match table_type { - // LuaType::Def(_) => return Some(false), - // _ => {} - // } if let Some(table_expr) = LuaTableExpr::cast(table_expr.syntax().clone()) { return check_table_expr_content(context, semantic_model, table_type, &table_expr); } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs index 2c9ae365b..0afacd5d1 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs @@ -1,8 +1,8 @@ use emmylua_parser::{LuaAst, LuaAstNode, LuaIndexExpr, LuaNameExpr}; use crate::{ - DiagnosticCode, LuaDeclId, LuaDeprecated, LuaMemberId, LuaSemanticDeclId, SemanticDeclLevel, - SemanticModel, + DiagnosticCode, LuaDeclId, LuaDeprecated, LuaMemberId, LuaSemanticDeclId, LuaType, + SemanticDeclLevel, SemanticModel, }; use super::{Checker, DiagnosticContext}; @@ -45,23 +45,12 @@ fn check_name_expr( return Some(()); } - let property = semantic_model - .get_db() - .get_property_index() - .get_property(&semantic_decl)?; - if let Some(deprecated) = property.deprecated() { - let deprecated_message = match deprecated { - LuaDeprecated::Deprecated => "deprecated".to_string(), - LuaDeprecated::DeprecatedWithMessage(message) => message.to_string(), - }; - - context.add_diagnostic( - DiagnosticCode::Deprecated, - name_expr.get_range(), - deprecated_message, - None, - ); - } + check_deprecated( + context, + semantic_model, + &semantic_decl, + name_expr.get_range(), + ); Some(()) } @@ -80,24 +69,47 @@ fn check_index_expr( { return Some(()); } + let index_name_range = index_expr.get_index_name_token()?.text_range(); + check_deprecated(context, semantic_model, &semantic_decl, index_name_range); + Some(()) +} + +fn check_deprecated( + context: &mut DiagnosticContext, + semantic_model: &SemanticModel, + semantic_decl: &LuaSemanticDeclId, + range: rowan::TextRange, +) { let property = semantic_model .get_db() .get_property_index() - .get_property(&semantic_decl)?; + .get_property(semantic_decl); + let Some(property) = property else { + return; + }; if let Some(deprecated) = property.deprecated() { let deprecated_message = match deprecated { LuaDeprecated::Deprecated => "deprecated".to_string(), LuaDeprecated::DeprecatedWithMessage(message) => message.to_string(), }; - let index_name_range = index_expr.get_index_name_token()?.text_range(); - - context.add_diagnostic( - DiagnosticCode::Deprecated, - index_name_range, - deprecated_message, - None, - ); + context.add_diagnostic(DiagnosticCode::Deprecated, range, deprecated_message, None); + } + // 检查特性 + if let Some(attribute_uses) = property.attribute_uses() { + for attribute_use in attribute_uses.iter() { + if attribute_use.id.get_name() == "deprecated" { + let depreacated_message = match attribute_use.params.first() { + Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(), + _ => "deprecated".to_string(), + }; + context.add_diagnostic( + DiagnosticCode::Deprecated, + range, + depreacated_message, + None, + ); + } + } } - Some(()) } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs index da345a082..8a52b7457 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs @@ -105,9 +105,9 @@ fn check_call_expr( && let Some(add_diagnostic) = check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(arg_expr.syntax().clone()), arg_expr, Some(¶m_type), - Some(arg_type), ) && add_diagnostic { diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/return_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/return_type_mismatch.rs index e69bf6aae..c8415cd13 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/return_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/return_type_mismatch.rs @@ -94,9 +94,9 @@ fn check_return_stat( check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(return_expr.syntax().clone()), return_expr, Some(check_type), - Some(return_expr_type), ); } @@ -132,9 +132,9 @@ fn check_return_stat( if let Some(add_diagnostic) = check_table_expr( context, semantic_model, + rowan::NodeOrToken::Node(return_expr.syntax().clone()), return_expr, Some(return_type), - Some(return_expr_type), ) && add_diagnostic { return Some(()); diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 2ae7a214d..51cb01302 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -708,7 +708,7 @@ pub fn parse_attribute_use(p: &mut LuaDocParser) -> DocParseResult { fn parse_doc_attribute_use(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocAttributeUse); - expect_token(p, LuaTokenKind::TkName)?; + parse_type(p)?; // 解析参数列表, 允许没有参数的特性在使用时省略括号 if p.current_token() == LuaTokenKind::TkLeftParen { diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 427f629f8..44f402ab4 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2913,11 +2913,13 @@ Syntax(Chunk)@0..105 Syntax(DocTagAttributeUse)@69..96 Token(TkDocAttribute)@69..70 "[" Syntax(DocAttributeUse)@70..74 - Token(TkName)@70..74 "Skip" + Syntax(TypeName)@70..74 + Token(TkName)@70..74 "Skip" Token(TkComma)@74..75 "," Token(TkWhitespace)@75..76 " " Syntax(DocAttributeUse)@76..95 - Token(TkName)@76..87 "check_point" + Syntax(TypeName)@76..87 + Token(TkName)@76..87 "check_point" Syntax(DocAttributeCallArgList)@87..95 Token(TkLeftParen)@87..88 "(" Syntax(LiteralExpr)@88..91 diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index 2274b4c2c..1567fc50c 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -518,8 +518,8 @@ impl LuaAstNode for LuaDocAttributeUse { } impl LuaDocAttributeUse { - pub fn get_name_token(&self) -> Option { - self.token() + pub fn get_type(&self) -> Option { + self.child() } pub fn get_attribute_call_arg_list(&self) -> Option { From 6dd62deb252b314cba8edacbf952578246350fd2 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 24 Sep 2025 22:52:21 +0800 Subject: [PATCH 05/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E5=88=AB=E5=90=8D=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/db_index/property/property.rs | 8 ++ .../add_completions/add_member_completion.rs | 73 ++++++++++++++++++- .../completion/add_completions/mod.rs | 2 +- .../emmylua_ls/src/handlers/completion/mod.rs | 2 +- .../handlers/inlay_hint/build_inlay_hint.rs | 4 +- .../src/handlers/test/completion_test.rs | 8 +- .../src/handlers/test/inlay_hint_test.rs | 6 +- 7 files changed, 90 insertions(+), 13 deletions(-) diff --git a/crates/emmylua_code_analysis/src/db_index/property/property.rs b/crates/emmylua_code_analysis/src/db_index/property/property.rs index 96f8d7208..f1c4251ef 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/property.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/property.rs @@ -109,6 +109,14 @@ impl LuaCommonProperty { pub fn attribute_uses(&self) -> Option<&Arc>> { self.attribute_uses.as_ref() } + + pub fn find_attribute_use(&self, id: LuaTypeDeclId) -> Option<&LuaAttributeUse> { + self.attribute_uses.as_ref().and_then(|attribute_uses| { + attribute_uses + .iter() + .find(|attribute_use| attribute_use.id == id) + }) + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs index aec80ea87..b9f5a3bba 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs @@ -1,5 +1,5 @@ use emmylua_code_analysis::{ - DbIndex, LuaMemberInfo, LuaMemberKey, LuaSemanticDeclId, LuaType, SemanticModel, + DbIndex, LuaMemberInfo, LuaMemberKey, LuaSemanticDeclId, LuaType, LuaTypeDeclId, SemanticModel, try_extract_signature_id_from_field, }; use emmylua_parser::{ @@ -129,8 +129,8 @@ pub fn add_member_completion( ); } - // 尝试添加别名补全项, 如果添加成功, 则不添加原本 `[index]` 补全项 - if !try_add_alias_completion_item(builder, &member_info, &completion_item, &label) + // 尝试添加别名补全项, 如果添加成功, 则不添加原来的 `[index]` 补全项 + if !try_add_alias_completion_item_new(builder, &member_info, &completion_item, &label) .unwrap_or(false) { builder.add_completion_item(completion_item)?; @@ -310,6 +310,69 @@ fn get_resolve_function_params_str(typ: &LuaType, display: CallDisplay) -> Optio } } +fn try_add_alias_completion_item_new( + builder: &mut CompletionBuilder, + member_info: &LuaMemberInfo, + completion_item: &CompletionItem, + label: &String, +) -> Option { + let alias_label = get_index_alias_name(&builder.semantic_model, member_info)?; + let mut alias_completion_item = completion_item.clone(); + alias_completion_item.label = alias_label; + alias_completion_item.insert_text = Some(label.clone()); + + // 更新 label_details 添加别名提示 + let index_hint = t!("completion.index %{label}", label = label).to_string(); + let label_details = alias_completion_item + .label_details + .get_or_insert_with(Default::default); + label_details.description = match label_details.description.take() { + Some(desc) => Some(format!("({}) {} ", index_hint, desc)), + None => Some(index_hint), + }; + builder.add_completion_item(alias_completion_item)?; + Some(true) +} + +pub fn get_index_alias_name( + semantic_model: &SemanticModel, + member_info: &LuaMemberInfo, +) -> Option { + let db = semantic_model.get_db(); + let LuaMemberKey::Integer(_) = member_info.key else { + return None; + }; + + let property_owner_id = member_info.property_owner_id.as_ref()?; + let LuaSemanticDeclId::Member(member_id) = property_owner_id else { + return None; + }; + let common_property = match db.get_property_index().get_property(property_owner_id) { + Some(common_property) => common_property, + None => { + // field定义的`signature`的`common_property`绑定位置稍有不同, 需要特殊处理 + let member = db.get_member_index().get_member(member_id)?; + let signature_id = + try_extract_signature_id_from_field(semantic_model.get_db(), member)?; + db.get_property_index() + .get_property(&LuaSemanticDeclId::Signature(signature_id))? + } + }; + + let alias_label = common_property + .find_attribute_use(LuaTypeDeclId::new("index_alias"))? + .params + .get(0) + .map(|param| match param { + LuaType::DocStringConst(s) => s.as_ref(), + _ => "", + })? + .to_string(); + Some(alias_label) +} + +#[deprecated] +#[allow(dead_code)] /// 添加索引成员的别名补全项 fn try_add_alias_completion_item( builder: &mut CompletionBuilder, @@ -317,6 +380,7 @@ fn try_add_alias_completion_item( completion_item: &CompletionItem, label: &String, ) -> Option { + #[allow(deprecated)] let alias_label = extract_index_member_alias(&builder.semantic_model, member_info)?; let mut alias_completion_item = completion_item.clone(); @@ -336,9 +400,10 @@ fn try_add_alias_completion_item( Some(true) } +#[deprecated] /// 从注释中提取索引成员的别名, 只处理整数成员. /// 格式为`-- [nameX]`. -pub fn extract_index_member_alias( +fn extract_index_member_alias( semantic_model: &SemanticModel, member_info: &LuaMemberInfo, ) -> Option { diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/mod.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/mod.rs index f9ad12346..53162ff91 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/mod.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/mod.rs @@ -3,7 +3,7 @@ mod add_member_completion; mod check_match_word; pub use add_decl_completion::add_decl_completion; -pub use add_member_completion::extract_index_member_alias; +pub use add_member_completion::get_index_alias_name; pub use add_member_completion::{CompletionTriggerStatus, add_member_completion}; pub use check_match_word::check_match_word; use emmylua_code_analysis::{LuaSemanticDeclId, LuaType, RenderLevel}; diff --git a/crates/emmylua_ls/src/handlers/completion/mod.rs b/crates/emmylua_ls/src/handlers/completion/mod.rs index 71aa20486..b93604f58 100644 --- a/crates/emmylua_ls/src/handlers/completion/mod.rs +++ b/crates/emmylua_ls/src/handlers/completion/mod.rs @@ -5,7 +5,7 @@ mod data; mod providers; mod resolve_completion; -pub use add_completions::extract_index_member_alias; +pub use add_completions::get_index_alias_name; use completion_builder::CompletionBuilder; use completion_data::CompletionData; use emmylua_code_analysis::{EmmyLuaAnalysis, FileId}; diff --git a/crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs b/crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs index 17e55fec4..fcb8d6750 100644 --- a/crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs +++ b/crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs @@ -17,7 +17,7 @@ use rowan::NodeOrToken; use rowan::TokenAtOffset; -use crate::handlers::completion::extract_index_member_alias; +use crate::handlers::completion::get_index_alias_name; use crate::handlers::definition::compare_function_types; use crate::handlers::inlay_hint::build_function_hint::{build_closure_hint, build_label_parts}; @@ -641,7 +641,7 @@ fn build_index_expr_hint( let member_infos = semantic_model.get_member_info_with_key(&prefix_type, member_key, false)?; let member_info = member_infos.first()?; // 尝试提取别名 - let alias = extract_index_member_alias(semantic_model, member_info)?; + let alias = get_index_alias_name(semantic_model, member_info)?; // 创建 hint let document = semantic_model.get_document(); let position = { diff --git a/crates/emmylua_ls/src/handlers/test/completion_test.rs b/crates/emmylua_ls/src/handlers/test/completion_test.rs index 5a9f85204..5fef9781b 100644 --- a/crates/emmylua_ls/src/handlers/test/completion_test.rs +++ b/crates/emmylua_ls/src/handlers/test/completion_test.rs @@ -1146,10 +1146,11 @@ mod tests { #[gtest] fn test_index_key_alias() -> Result<()> { let mut ws = ProviderVirtualWorkspace::new(); + ws.def(" ---@attribute index_alias(name: string)"); check!(ws.check_completion( r#" local export = { - [1] = 1, -- [nameX] + [1] = 1, ---@[index_alias("nameX")] } export. @@ -1215,11 +1216,12 @@ mod tests { #[gtest] fn test_field_index_function() -> Result<()> { - let mut ws = ProviderVirtualWorkspace::new(); + let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib(); ws.def( r#" ---@class A - ---@field [1] fun() # [next] + ---@[index_alias("next")] + ---@field [1] fun() A = {} "#, ); diff --git a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs index 58dd01dcc..b1ea188c8 100644 --- a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs +++ b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs @@ -167,16 +167,18 @@ mod tests { #[gtest] fn test_index_key_alias_hint() -> Result<()> { let mut ws = ProviderVirtualWorkspace::new(); + ws.def(" ---@attribute index_alias(name: string)"); check!(ws.check_inlay_hint( r#" local export = { - [1] = 1, -- [nameX] + ---@[index_alias("nameX")] + [1] = 1, } print(export[1]) "#, vec![VirtualInlayHint { label: ": nameX".to_string(), - line: 4, + line: 5, pos: 30, ref_file: Some("".to_string()), }] From 300e7bf6d23fb3e73c4e5496da27034719b070f7 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 24 Sep 2025 22:53:21 +0800 Subject: [PATCH 06/30] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8E=9F=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E5=88=AB=E5=90=8D(index=20field=20alias)=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add_completions/add_member_completion.rs | 90 ------------------- 1 file changed, 90 deletions(-) diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs index b9f5a3bba..e718a1d8d 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs @@ -370,93 +370,3 @@ pub fn get_index_alias_name( .to_string(); Some(alias_label) } - -#[deprecated] -#[allow(dead_code)] -/// 添加索引成员的别名补全项 -fn try_add_alias_completion_item( - builder: &mut CompletionBuilder, - member_info: &LuaMemberInfo, - completion_item: &CompletionItem, - label: &String, -) -> Option { - #[allow(deprecated)] - let alias_label = extract_index_member_alias(&builder.semantic_model, member_info)?; - - let mut alias_completion_item = completion_item.clone(); - alias_completion_item.label = alias_label; - alias_completion_item.insert_text = Some(label.clone()); - - // 更新 label_details 添加别名提示 - let index_hint = t!("completion.index %{label}", label = label).to_string(); - let label_details = alias_completion_item - .label_details - .get_or_insert_with(Default::default); - label_details.description = match label_details.description.take() { - Some(desc) => Some(format!("({}) {} ", index_hint, desc)), - None => Some(index_hint), - }; - builder.add_completion_item(alias_completion_item)?; - Some(true) -} - -#[deprecated] -/// 从注释中提取索引成员的别名, 只处理整数成员. -/// 格式为`-- [nameX]`. -fn extract_index_member_alias( - semantic_model: &SemanticModel, - member_info: &LuaMemberInfo, -) -> Option { - let db = semantic_model.get_db(); - let LuaMemberKey::Integer(_) = member_info.key else { - return None; - }; - - let property_owner_id = member_info.property_owner_id.as_ref()?; - let LuaSemanticDeclId::Member(member_id) = property_owner_id else { - return None; - }; - - let common_property = match db.get_property_index().get_property(property_owner_id) { - Some(common_property) => common_property, - None => { - // field定义的`signature`的`common_property`绑定位置稍有不同, 需要特殊处理 - let member = db.get_member_index().get_member(member_id)?; - let signature_id = - try_extract_signature_id_from_field(semantic_model.get_db(), member)?; - db.get_property_index() - .get_property(&LuaSemanticDeclId::Signature(signature_id))? - } - }; - - let description = common_property.description()?; - - // 只去掉左侧空白字符,保留右侧内容以支持后续文本 - let left_trimmed = description.trim_start(); - if !left_trimmed.starts_with('[') { - return None; - } - - // 找到对应的右方括号 - let close_bracket_pos = left_trimmed.find(']')?; - - let content = left_trimmed[1..close_bracket_pos].trim(); - - if content.is_empty() { - return None; - } - - let first_char = content.chars().next()?; - if !first_char.is_alphabetic() && first_char != '_' { - return None; - } - - if !content.chars().all(|c| c.is_alphanumeric() || c == '_') { - return None; - } - if content.parse::().is_ok() || content.parse::().is_ok() { - return None; - } - - Some(content.to_string()) -} From e530d7c75a02e012ecef38825fc1a39465db31df Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 01:20:50 +0800 Subject: [PATCH 07/30] `attribute` add hover and completion --- crates/emmylua_ls/locales/tags/en.yaml | 12 +++++ crates/emmylua_ls/locales/tags/zh_CN.yaml | 12 +++++ crates/emmylua_ls/locales/tags/zh_HK.yaml | 12 +++++ .../src/handlers/completion/data/doc_tags.rs | 1 + .../completion/providers/desc_provider.rs | 4 +- .../completion/providers/doc_type_provider.rs | 51 +++++++++++++++---- .../src/handlers/hover/build_hover.rs | 2 + 7 files changed, 81 insertions(+), 13 deletions(-) diff --git a/crates/emmylua_ls/locales/tags/en.yaml b/crates/emmylua_ls/locales/tags/en.yaml index 2ed9ac9b8..fa7aed3e9 100644 --- a/crates/emmylua_ls/locales/tags/en.yaml +++ b/crates/emmylua_ls/locales/tags/en.yaml @@ -245,3 +245,15 @@ tags.language: | INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); ]] ``` +tags.attribute: | + `attribute` tag defines an attribute. Attribute is used to attach extra information to a definition. + Example: + ```lua + ---@attribute deprecated(message: string?) + + ---@class A + ---@[deprecated("delete")] # `b` field is marked as deprecated + ---@field b string + ---@[deprecated] # If `attribute` allows no parameters, the parentheses can be omitted + ---@field c string + ``` diff --git a/crates/emmylua_ls/locales/tags/zh_CN.yaml b/crates/emmylua_ls/locales/tags/zh_CN.yaml index b6e0a04e0..df8075a92 100644 --- a/crates/emmylua_ls/locales/tags/zh_CN.yaml +++ b/crates/emmylua_ls/locales/tags/zh_CN.yaml @@ -245,3 +245,15 @@ tags.language: | INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); ]] ``` +tags.attribute: | + `attribute` 标签定义一个特性。特性用于附加额外信息到定义。 + 示例: + ```lua + ---@attribute deprecated(message: string?) + + ---@class A + ---@[deprecated("delete")] + ---@field b string # `b` 字段被标记为已弃用 + ---@[deprecated] # 如果`attribute`允许无参数,则可以省略括号 + ---@field c string + ``` diff --git a/crates/emmylua_ls/locales/tags/zh_HK.yaml b/crates/emmylua_ls/locales/tags/zh_HK.yaml index 4cdf3230b..4c4eb8ef8 100644 --- a/crates/emmylua_ls/locales/tags/zh_HK.yaml +++ b/crates/emmylua_ls/locales/tags/zh_HK.yaml @@ -243,3 +243,15 @@ tags.language: | INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); ]] ``` +tags.attribute: | + `attribute` 標籤定義一個特性。特性用於附加額外信息到定義。 + 示例: + ```lua + ---@attribute deprecated(message: string?) + + ---@class A + ---@[deprecated("delete")] # `b` 字段被標記為已棄用 + ---@field b string + ---@[deprecated] # 如果`attribute`允許無參數,則可以省略括號 + ---@field c string + ``` diff --git a/crates/emmylua_ls/src/handlers/completion/data/doc_tags.rs b/crates/emmylua_ls/src/handlers/completion/data/doc_tags.rs index 3760cc942..c0f764362 100644 --- a/crates/emmylua_ls/src/handlers/completion/data/doc_tags.rs +++ b/crates/emmylua_ls/src/handlers/completion/data/doc_tags.rs @@ -32,4 +32,5 @@ pub const DOC_TAGS: &[&str] = &[ "return_cast", "export", "language", + "attribute", ]; diff --git a/crates/emmylua_ls/src/handlers/completion/providers/desc_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/desc_provider.rs index f2c6ae390..4a3315aa6 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/desc_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/desc_provider.rs @@ -87,7 +87,7 @@ fn add_global_completions(builder: &mut CompletionBuilder) -> Option<()> { } // Types in namespaces. - complete_types_by_prefix(builder, "", Some(&seen_types)); + complete_types_by_prefix(builder, "", Some(&seen_types), None); // Types in current module. if let Some(module) = builder.semantic_model.get_module() @@ -167,7 +167,7 @@ fn add_by_prefix( // Modules. add_modules(builder, &prefix, None); - complete_types_by_prefix(builder, &prefix, Some(&seen_types)); + complete_types_by_prefix(builder, &prefix, Some(&seen_types), None); } None diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs index 3b4d6c935..31b048ad4 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs @@ -1,5 +1,5 @@ use emmylua_code_analysis::LuaTypeDeclId; -use emmylua_parser::{LuaAstNode, LuaDocNameType, LuaSyntaxKind, LuaTokenKind}; +use emmylua_parser::{LuaAstNode, LuaDocAttributeUse, LuaDocNameType, LuaSyntaxKind, LuaTokenKind}; use lsp_types::CompletionItem; use std::collections::HashSet; @@ -12,7 +12,7 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { return None; } - check_can_add_type_completion(builder)?; + let completion_type = check_can_add_type_completion(builder)?; let prefix_content = builder.trigger_token.text().to_string(); let prefix = if let Some(last_sep) = prefix_content.rfind('.') { @@ -21,7 +21,7 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { } else { "" }; - complete_types_by_prefix(builder, prefix, None); + complete_types_by_prefix(builder, prefix, None, Some(completion_type)); builder.stop_here(); Some(()) } @@ -30,7 +30,9 @@ pub fn complete_types_by_prefix( builder: &mut CompletionBuilder, prefix: &str, filter: Option<&HashSet>, + completion_type: Option, ) -> Option<()> { + let completion_type = completion_type.or(Some(CompletionType::Type))?; let file_id = builder.semantic_model.get_file_id(); let type_index = builder.semantic_model.get_db().get_type_index(); let results = type_index.find_type_decls(file_id, prefix); @@ -43,18 +45,42 @@ pub fn complete_types_by_prefix( { continue; } - add_type_completion_item(builder, &name, type_decl); + match completion_type { + CompletionType::Attribute => { + if let Some(decl_id) = type_decl { + let type_decl = builder + .semantic_model + .get_db() + .get_type_index() + .get_type_decl(&decl_id)?; + if type_decl.is_attribute() { + add_type_completion_item(builder, &name, Some(decl_id)); + } + } + } + CompletionType::Type => { + add_type_completion_item(builder, &name, type_decl); + } + } } Some(()) } -fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option<()> { +pub enum CompletionType { + Type, + Attribute, +} + +fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option { match builder.trigger_token.kind().into() { LuaTokenKind::TkName => { let parent = builder.trigger_token.parent()?; - if LuaDocNameType::cast(parent).is_some() { - return Some(()); + if let Some(doc_name) = LuaDocNameType::cast(parent) { + if let Some(_) = doc_name.get_parent::() { + return Some(CompletionType::Attribute); + } + return Some(CompletionType::Type); } None @@ -63,7 +89,7 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option<()> { let left_token = builder.trigger_token.prev_token()?; match left_token.kind().into() { LuaTokenKind::TkTagReturn | LuaTokenKind::TkTagType => { - return Some(()); + return Some(CompletionType::Type); } LuaTokenKind::TkName => { let parent = left_token.parent()?; @@ -71,20 +97,20 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option<()> { LuaSyntaxKind::DocTagParam | LuaSyntaxKind::DocTagField | LuaSyntaxKind::DocTagAlias - | LuaSyntaxKind::DocTagCast => return Some(()), + | LuaSyntaxKind::DocTagCast => return Some(CompletionType::Type), _ => {} } } LuaTokenKind::TkComma | LuaTokenKind::TkDocOr => { let parent = left_token.parent()?; if parent.kind() == LuaSyntaxKind::DocTypeList.into() { - return Some(()); + return Some(CompletionType::Type); } } LuaTokenKind::TkColon => { let parent = left_token.parent()?; if parent.kind() == LuaSyntaxKind::DocTagClass.into() { - return Some(()); + return Some(CompletionType::Type); } } _ => {} @@ -92,6 +118,9 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option<()> { None } + LuaTokenKind::TkDocAttribute => { + return Some(CompletionType::Attribute); + } _ => None, } } diff --git a/crates/emmylua_ls/src/handlers/hover/build_hover.rs b/crates/emmylua_ls/src/handlers/hover/build_hover.rs index 263c109d8..f5b890f17 100644 --- a/crates/emmylua_ls/src/handlers/hover/build_hover.rs +++ b/crates/emmylua_ls/src/handlers/hover/build_hover.rs @@ -300,6 +300,8 @@ fn build_type_decl_hover( } } else if type_decl.is_enum() { format!("(enum) {}", type_decl.get_name()) + } else if type_decl.is_attribute() { + format!("(attribute) {}", type_decl.get_name()) } else { let humanize_text = humanize_type( db, From 0d81fee4b1472a0eca9a113bebf0ea692c7a08df Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 02:58:26 +0800 Subject: [PATCH 08/30] =?UTF-8?q?=E6=95=B4=E7=90=86=20hover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/db_index/type/type_decl.rs | 8 +++ .../src/handlers/hover/build_hover.rs | 40 ++----------- .../src/handlers/hover/hover_builder.rs | 2 +- ...ction_humanize.rs => humanize_function.rs} | 2 +- .../src/handlers/hover/humanize_type_decl.rs | 59 +++++++++++++++++++ .../{hover_humanize.rs => humanize_types.rs} | 0 crates/emmylua_ls/src/handlers/hover/mod.rs | 7 ++- 7 files changed, 78 insertions(+), 40 deletions(-) rename crates/emmylua_ls/src/handlers/hover/{function_humanize.rs => humanize_function.rs} (99%) create mode 100644 crates/emmylua_ls/src/handlers/hover/humanize_type_decl.rs rename crates/emmylua_ls/src/handlers/hover/{hover_humanize.rs => humanize_types.rs} (100%) diff --git a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs index ff4e35c3b..7a7619372 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs @@ -178,6 +178,14 @@ impl LuaTypeDecl { } } + pub fn get_attribute_type(&self) -> Option<&LuaType> { + if let LuaTypeExtra::Attribute { typ: Some(typ) } = &self.extra { + Some(typ) + } else { + None + } + } + pub fn merge_decl(&mut self, other: LuaTypeDecl) { self.locations.extend(other.locations); } diff --git a/crates/emmylua_ls/src/handlers/hover/build_hover.rs b/crates/emmylua_ls/src/handlers/hover/build_hover.rs index f5b890f17..251efddb8 100644 --- a/crates/emmylua_ls/src/handlers/hover/build_hover.rs +++ b/crates/emmylua_ls/src/handlers/hover/build_hover.rs @@ -3,22 +3,23 @@ use std::collections::HashSet; use emmylua_code_analysis::humanize_type; use emmylua_code_analysis::{ DbIndex, LuaCompilation, LuaDeclId, LuaDocument, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, - LuaSignatureId, LuaType, LuaTypeDeclId, RenderLevel, SemanticInfo, SemanticModel, + LuaSignatureId, LuaType, RenderLevel, SemanticInfo, SemanticModel, }; use emmylua_parser::{LuaAssignStat, LuaAstNode, LuaExpr, LuaSyntaxToken}; use lsp_types::{Hover, HoverContents, MarkedString, MarkupContent}; use rowan::TextRange; +use crate::handlers::hover::humanize_type_decl::build_type_decl_hover; use crate::handlers::hover::{ find_origin::replace_semantic_type, - function_humanize::{hover_function_type, is_function}, - hover_humanize::hover_humanize_type, + humanize_function::{hover_function_type, is_function}, + humanize_types::hover_humanize_type, }; use super::{ find_origin::{find_decl_origin_owners, find_member_origin_owners}, hover_builder::HoverBuilder, - hover_humanize::hover_const_type, + humanize_types::hover_const_type, }; pub fn build_semantic_info_hover( @@ -285,37 +286,6 @@ fn build_member_hover( Some(()) } -fn build_type_decl_hover( - builder: &mut HoverBuilder, - db: &DbIndex, - type_decl_id: LuaTypeDeclId, -) -> Option<()> { - let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?; - let type_description = if type_decl.is_alias() { - if let Some(origin) = type_decl.get_alias_origin(db, None) { - let origin_type = humanize_type(db, &origin, builder.detail_render_level); - format!("(alias) {} = {}", type_decl.get_name(), origin_type) - } else { - "".to_string() - } - } else if type_decl.is_enum() { - format!("(enum) {}", type_decl.get_name()) - } else if type_decl.is_attribute() { - format!("(attribute) {}", type_decl.get_name()) - } else { - let humanize_text = humanize_type( - db, - &LuaType::Def(type_decl_id.clone()), - builder.detail_render_level, - ); - format!("(class) {}", humanize_text) - }; - - builder.set_type_description(type_description); - builder.add_description(&LuaSemanticDeclId::TypeDecl(type_decl_id)); - Some(()) -} - pub fn add_signature_param_description( db: &DbIndex, marked_strings: &mut Vec, diff --git a/crates/emmylua_ls/src/handlers/hover/hover_builder.rs b/crates/emmylua_ls/src/handlers/hover/hover_builder.rs index 354acfe5d..b1ff422f9 100644 --- a/crates/emmylua_ls/src/handlers/hover/hover_builder.rs +++ b/crates/emmylua_ls/src/handlers/hover/hover_builder.rs @@ -5,7 +5,7 @@ use emmylua_code_analysis::{ use emmylua_parser::{LuaAstNode, LuaCallExpr, LuaSyntaxToken}; use lsp_types::{Hover, HoverContents, MarkedString, MarkupContent}; -use crate::handlers::hover::hover_humanize::{ +use crate::handlers::hover::humanize_types::{ DescriptionInfo, extract_description_from_property_owner, }; diff --git a/crates/emmylua_ls/src/handlers/hover/function_humanize.rs b/crates/emmylua_ls/src/handlers/hover/humanize_function.rs similarity index 99% rename from crates/emmylua_ls/src/handlers/hover/function_humanize.rs rename to crates/emmylua_ls/src/handlers/hover/humanize_function.rs index 7038f4872..bc0309698 100644 --- a/crates/emmylua_ls/src/handlers/hover/function_humanize.rs +++ b/crates/emmylua_ls/src/handlers/hover/humanize_function.rs @@ -10,7 +10,7 @@ use crate::handlers::{ definition::extract_semantic_decl_from_signature, hover::{ HoverBuilder, - hover_humanize::{ + humanize_types::{ DescriptionInfo, extract_description_from_property_owner, extract_owner_name_from_element, hover_humanize_type, }, diff --git a/crates/emmylua_ls/src/handlers/hover/humanize_type_decl.rs b/crates/emmylua_ls/src/handlers/hover/humanize_type_decl.rs new file mode 100644 index 000000000..34e1048cf --- /dev/null +++ b/crates/emmylua_ls/src/handlers/hover/humanize_type_decl.rs @@ -0,0 +1,59 @@ +use emmylua_code_analysis::{ + DbIndex, LuaSemanticDeclId, LuaType, LuaTypeDeclId, RenderLevel, humanize_type, +}; + +use crate::handlers::hover::HoverBuilder; + +pub fn build_type_decl_hover( + builder: &mut HoverBuilder, + db: &DbIndex, + type_decl_id: LuaTypeDeclId, +) -> Option<()> { + let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?; + let type_description = if type_decl.is_alias() { + if let Some(origin) = type_decl.get_alias_origin(db, None) { + let origin_type = humanize_type(db, &origin, builder.detail_render_level); + format!("(alias) {} = {}", type_decl.get_name(), origin_type) + } else { + "".to_string() + } + } else if type_decl.is_enum() { + format!("(enum) {}", type_decl.get_name()) + } else if type_decl.is_attribute() { + build_attribute(db, type_decl.get_name(), type_decl.get_attribute_type()) + } else { + let humanize_text = humanize_type( + db, + &LuaType::Def(type_decl_id.clone()), + builder.detail_render_level, + ); + format!("(class) {}", humanize_text) + }; + + builder.set_type_description(type_description); + builder.add_description(&LuaSemanticDeclId::TypeDecl(type_decl_id)); + Some(()) +} + +fn build_attribute(db: &DbIndex, attribute_name: &str, attribute_type: Option<&LuaType>) -> String { + let Some(LuaType::DocAttribute(attribute)) = attribute_type else { + return format!("(attribute) {}", attribute_name); + }; + let params = attribute + .get_params() + .iter() + .map(|(name, typ)| match typ { + Some(typ) => { + let type_name = humanize_type(db, typ, RenderLevel::Normal); + format!("{}: {}", name, type_name) + } + None => name.to_string(), + }) + .collect::>(); + + if params.is_empty() { + format!("(attribute) {}", attribute_name) + } else { + format!("(attribute) {}({})", attribute_name, params.join(", ")) + } +} diff --git a/crates/emmylua_ls/src/handlers/hover/hover_humanize.rs b/crates/emmylua_ls/src/handlers/hover/humanize_types.rs similarity index 100% rename from crates/emmylua_ls/src/handlers/hover/hover_humanize.rs rename to crates/emmylua_ls/src/handlers/hover/humanize_types.rs diff --git a/crates/emmylua_ls/src/handlers/hover/mod.rs b/crates/emmylua_ls/src/handlers/hover/mod.rs index 4aabcbb21..9a38e050e 100644 --- a/crates/emmylua_ls/src/handlers/hover/mod.rs +++ b/crates/emmylua_ls/src/handlers/hover/mod.rs @@ -1,8 +1,9 @@ mod build_hover; mod find_origin; -mod function_humanize; mod hover_builder; -mod hover_humanize; +mod humanize_function; +mod humanize_type_decl; +mod humanize_types; mod keyword_hover; mod std_hover; @@ -16,7 +17,7 @@ use emmylua_parser::{LuaAstNode, LuaDocDescription, LuaTokenKind}; use emmylua_parser_desc::parse_ref_target; pub use find_origin::{find_all_same_named_members, find_member_origin_owner}; pub use hover_builder::HoverBuilder; -pub use hover_humanize::infer_prefix_global_name; +pub use humanize_types::infer_prefix_global_name; use keyword_hover::{hover_keyword, is_keyword}; use lsp_types::{ ClientCapabilities, Hover, HoverContents, HoverParams, HoverProviderCapability, MarkupContent, From 812f0f2bb099c6c8ee320515a4f618f587bacd0c Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 04:53:09 +0800 Subject: [PATCH 09/30] `attribute` add diagnostic --- .../resources/schema.json | 15 ++ .../analyzer/doc/attribute_tags.rs | 2 +- .../src/diagnostic/checker/attribute_check.rs | 234 ++++++++++++++++++ .../checker/generic/infer_doc_type.rs | 49 +++- .../src/diagnostic/checker/mod.rs | 2 + .../src/diagnostic/lua_diagnostic_code.rs | 6 + .../emmylua_parser/src/syntax/node/doc/mod.rs | 2 +- 7 files changed, 301 insertions(+), 9 deletions(-) create mode 100644 crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs diff --git a/crates/emmylua_code_analysis/resources/schema.json b/crates/emmylua_code_analysis/resources/schema.json index 2c42c83d5..7296ce833 100644 --- a/crates/emmylua_code_analysis/resources/schema.json +++ b/crates/emmylua_code_analysis/resources/schema.json @@ -435,6 +435,21 @@ "description": "Global variable defined in non-module scope", "type": "string", "const": "global-in-non-module" + }, + { + "description": "attribute-param-type-mismatch", + "type": "string", + "const": "attribute-param-type-mismatch" + }, + { + "description": "attribute-missing-parameter", + "type": "string", + "const": "attribute-missing-parameter" + }, + { + "description": "attribute-redundant-parameter", + "type": "string", + "const": "attribute-redundant-parameter" } ] }, diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs index 452d70705..847651517 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -28,7 +28,7 @@ pub fn analyze_tag_attribute_use( let attribute_uses = attribute_use.get_attribute_uses(); for attribute_use in attribute_uses { let mut params = Vec::new(); - if let Some(attribute_call_arg_list) = attribute_use.get_attribute_call_arg_list() { + if let Some(attribute_call_arg_list) = attribute_use.get_arg_list() { for arg in attribute_call_arg_list.get_args() { let arg_type = infer_attribute_arg_type(arg); params.push(arg_type); diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs new file mode 100644 index 000000000..0e3d9764a --- /dev/null +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs @@ -0,0 +1,234 @@ +use std::collections::HashSet; + +use crate::{ + DiagnosticCode, LuaType, SemanticModel, TypeCheckFailReason, TypeCheckResult, + diagnostic::checker::{generic::infer_doc_type::infer_doc_type, humanize_lint_type}, +}; +use emmylua_parser::{ + LuaAstNode, LuaDocAttributeUse, LuaDocTagAttributeUse, LuaDocType, LuaExpr, LuaLiteralExpr, +}; +use rowan::TextRange; + +use super::{Checker, DiagnosticContext}; + +pub struct AttributeCheckChecker; + +impl Checker for AttributeCheckChecker { + const CODES: &[DiagnosticCode] = &[ + DiagnosticCode::AttributeParamTypeMismatch, + DiagnosticCode::AttributeMissingParameter, + DiagnosticCode::AttributeRedundantParameter, + ]; + + fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) { + let root = semantic_model.get_root().clone(); + for tag_use in root.descendants::() { + for attribute_use in tag_use.get_attribute_uses() { + check_attribute_use(context, semantic_model, &attribute_use); + } + } + } +} + +fn check_attribute_use( + context: &mut DiagnosticContext, + semantic_model: &SemanticModel, + attribute_use: &LuaDocAttributeUse, +) -> Option<()> { + let attribute_type = + infer_doc_type(semantic_model, &LuaDocType::Name(attribute_use.get_type()?)); + let LuaType::Ref(type_id) = attribute_type else { + return None; + }; + let type_decl = semantic_model + .get_db() + .get_type_index() + .get_type_decl(&type_id)?; + if !type_decl.is_attribute() { + return None; + } + let LuaType::DocAttribute(attr_def) = type_decl.get_attribute_type()? else { + return None; + }; + + let def_params = attr_def.get_params(); + let args = match attribute_use.get_arg_list() { + Some(arg_list) => arg_list.get_args().collect::>(), + None => vec![], + }; + check_param_count(context, &def_params, &attribute_use, &args); + check_param(context, semantic_model, &def_params, args); + + Some(()) +} + +/// 检查参数数量是否匹配 +fn check_param_count( + context: &mut DiagnosticContext, + def_params: &[(String, Option)], + attribute_use: &LuaDocAttributeUse, + args: &Vec, +) -> Option<()> { + let call_args_count = args.len(); + // 调用参数少于定义参数, 需要考虑可空参数 + if call_args_count < def_params.len() { + for def_param in def_params[call_args_count..].iter() { + if def_param.0 == "..." { + break; + } + if def_param.1.as_ref().is_some_and(|typ| is_nullable(typ)) { + continue; + } + context.add_diagnostic( + DiagnosticCode::AttributeMissingParameter, + match args.last() { + Some(arg) => arg.get_range(), + None => attribute_use.get_range(), + }, + t!( + "expected %{num} parameters but found %{found_num}", + num = def_params.len(), + found_num = call_args_count + ) + .to_string(), + None, + ); + } + } + // 调用参数多于定义参数, 需要考虑可变参数 + else if call_args_count > def_params.len() { + // 参数定义中最后一个参数是 `...` + if def_params.last().is_some_and(|(name, typ)| { + name == "..." || typ.as_ref().is_some_and(|typ| typ.is_variadic()) + }) { + return Some(()); + } + for arg in args[def_params.len()..].iter() { + context.add_diagnostic( + DiagnosticCode::AttributeRedundantParameter, + arg.get_range(), + t!( + "expected %{num} parameters but found %{found_num}", + num = def_params.len(), + found_num = call_args_count + ) + .to_string(), + None, + ); + } + } + + Some(()) +} + +/// 检查参数是否匹配 +fn check_param( + context: &mut DiagnosticContext, + semantic_model: &SemanticModel, + def_params: &[(String, Option)], + args: Vec, +) -> Option<()> { + let mut call_arg_types = Vec::new(); + for arg in &args { + let arg_type = semantic_model + .infer_expr(LuaExpr::LiteralExpr(arg.clone())) + .ok()?; + call_arg_types.push(arg_type); + } + + for (idx, param) in def_params.iter().enumerate() { + if param.0 == "..." { + if call_arg_types.len() < idx { + break; + } + if let Some(variadic_type) = param.1.clone() { + for arg_type in call_arg_types[idx..].iter() { + let result = semantic_model.type_check_detail(&variadic_type, arg_type); + if result.is_err() { + add_type_check_diagnostic( + context, + semantic_model, + args.get(idx)?.get_range(), + &variadic_type, + arg_type, + result, + ); + } + } + } + break; + } + if let Some(param_type) = param.1.clone() { + let arg_type = call_arg_types.get(idx).unwrap_or(&LuaType::Any); + let result = semantic_model.type_check_detail(¶m_type, arg_type); + if result.is_err() { + add_type_check_diagnostic( + context, + semantic_model, + args.get(idx)?.get_range(), + ¶m_type, + arg_type, + result, + ); + } + } + } + Some(()) +} + +fn add_type_check_diagnostic( + context: &mut DiagnosticContext, + semantic_model: &SemanticModel, + range: TextRange, + param_type: &LuaType, + expr_type: &LuaType, + result: TypeCheckResult, +) { + let db = semantic_model.get_db(); + match result { + Ok(_) => (), + Err(reason) => { + let reason_message = match reason { + TypeCheckFailReason::TypeNotMatchWithReason(reason) => reason, + TypeCheckFailReason::TypeNotMatch | TypeCheckFailReason::DonotCheck => { + "".to_string() + } + TypeCheckFailReason::TypeRecursion => "type recursion".to_string(), + }; + context.add_diagnostic( + DiagnosticCode::AttributeParamTypeMismatch, + range, + t!( + "expected `%{source}` but found `%{found}`. %{reason}", + source = humanize_lint_type(db, param_type), + found = humanize_lint_type(db, expr_type), + reason = reason_message + ) + .to_string(), + None, + ); + } + } +} + +fn is_nullable(typ: &LuaType) -> bool { + let mut stack: Vec = Vec::new(); + stack.push(typ.clone()); + let mut visited = HashSet::new(); + while let Some(typ) = stack.pop() { + if visited.contains(&typ) { + continue; + } + visited.insert(typ.clone()); + match typ { + LuaType::Any | LuaType::Unknown | LuaType::Nil => return true, + LuaType::Union(u) => { + for t in u.into_vec() { + stack.push(t); + } + } + _ => {} + } + } + false +} diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/generic/infer_doc_type.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/generic/infer_doc_type.rs index 94ef3d6f6..520d25647 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/generic/infer_doc_type.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/generic/infer_doc_type.rs @@ -1,19 +1,19 @@ use std::sync::Arc; use emmylua_parser::{ - LuaAstNode, LuaDocBinaryType, LuaDocDescriptionOwner, LuaDocFuncType, LuaDocGenericType, - LuaDocMultiLineUnionType, LuaDocObjectFieldKey, LuaDocObjectType, LuaDocStrTplType, LuaDocType, - LuaDocUnaryType, LuaDocVariadicType, LuaLiteralToken, LuaSyntaxKind, LuaTypeBinaryOperator, - LuaTypeUnaryOperator, + LuaAstNode, LuaDocAttributeType, LuaDocBinaryType, LuaDocDescriptionOwner, LuaDocFuncType, + LuaDocGenericType, LuaDocMultiLineUnionType, LuaDocObjectFieldKey, LuaDocObjectType, + LuaDocStrTplType, LuaDocType, LuaDocUnaryType, LuaDocVariadicType, LuaLiteralToken, + LuaSyntaxKind, LuaTypeBinaryOperator, LuaTypeUnaryOperator, }; use rowan::TextRange; use smol_str::SmolStr; use crate::{ AsyncState, InFiled, LuaAliasCallKind, LuaAliasCallType, LuaArrayLen, LuaArrayType, - LuaFunctionType, LuaGenericType, LuaIndexAccessKey, LuaIntersectionType, LuaMultiLineUnion, - LuaObjectType, LuaStringTplType, LuaTupleStatus, LuaTupleType, LuaType, LuaTypeDeclId, - SemanticModel, TypeOps, VariadicType, + LuaAttributeType, LuaFunctionType, LuaGenericType, LuaIndexAccessKey, LuaIntersectionType, + LuaMultiLineUnion, LuaObjectType, LuaStringTplType, LuaTupleStatus, LuaTupleType, LuaType, + LuaTypeDeclId, SemanticModel, TypeOps, VariadicType, }; pub fn infer_doc_type(semantic_model: &SemanticModel, node: &LuaDocType) -> LuaType { @@ -108,6 +108,9 @@ pub fn infer_doc_type(semantic_model: &SemanticModel, node: &LuaDocType) -> LuaT LuaDocType::MultiLineUnion(multi_union) => { return infer_multi_line_union_type(semantic_model, multi_union); } + LuaDocType::Attribute(attribute_type) => { + return infer_attribute_type(semantic_model, attribute_type); + } _ => {} } LuaType::Unknown @@ -563,3 +566,35 @@ fn infer_multi_line_union_type( LuaType::MultiLineUnion(LuaMultiLineUnion::new(union_members).into()) } + +fn infer_attribute_type( + semantic_model: &SemanticModel, + attribute_type: &LuaDocAttributeType, +) -> LuaType { + let mut params_result = Vec::new(); + for param in attribute_type.get_params() { + let name = if let Some(param) = param.get_name_token() { + param.get_name_text().to_string() + } else if param.is_dots() { + "...".to_string() + } else { + continue; + }; + + let nullable = param.is_nullable(); + + let type_ref = if let Some(type_ref) = param.get_type() { + let mut typ = infer_doc_type(semantic_model, &type_ref); + if nullable && !typ.is_nullable() { + typ = TypeOps::Union.apply(semantic_model.get_db(), &typ, &LuaType::Nil); + } + Some(typ) + } else { + None + }; + + params_result.push((name, type_ref)); + } + + LuaType::DocAttribute(LuaAttributeType::new(params_result).into()) +} diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/mod.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/mod.rs index a94f5899e..de13d6316 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/mod.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/mod.rs @@ -1,6 +1,7 @@ mod access_invisible; mod analyze_error; mod assign_type_mismatch; +mod attribute_check; mod await_in_sync; mod cast_type_mismatch; mod check_field; @@ -111,6 +112,7 @@ pub fn check_file(context: &mut DiagnosticContext, semantic_model: &SemanticMode run_check::(context, semantic_model); run_check::(context, semantic_model); run_check::(context, semantic_model); + run_check::(context, semantic_model); run_check::( context, diff --git a/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs b/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs index a8b697c63..8460a7eed 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs @@ -107,6 +107,12 @@ pub enum DiagnosticCode { ReadOnly, /// Global variable defined in non-module scope GlobalInNonModule, + /// attribute-param-type-mismatch + AttributeParamTypeMismatch, + /// attribute-missing-parameter + AttributeMissingParameter, + /// attribute-redundant-parameter + AttributeRedundantParameter, #[serde(other)] None, diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index 1567fc50c..37aed966d 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -522,7 +522,7 @@ impl LuaDocAttributeUse { self.child() } - pub fn get_attribute_call_arg_list(&self) -> Option { + pub fn get_arg_list(&self) -> Option { self.child() } } From 30c9c9906df44687d9b4dcf9cc95ea5ad3717a28 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 05:50:00 +0800 Subject: [PATCH 10/30] optimize `attribute` completion --- .../handlers/completion/providers/doc_type_provider.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs index 31b048ad4..e4bfc9225 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs @@ -59,6 +59,16 @@ pub fn complete_types_by_prefix( } } CompletionType::Type => { + if let Some(decl_id) = &type_decl { + let type_decl = builder + .semantic_model + .get_db() + .get_type_index() + .get_type_decl(decl_id)?; + if type_decl.is_attribute() { + continue; + } + } add_type_completion_item(builder, &name, type_decl); } } From a3f535a355ffe2b38b95b1ef5fc99c55576fd5bf Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 06:05:26 +0800 Subject: [PATCH 11/30] update CHANGELOG.md --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dba48860d..a48d41bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ *All notable changes to the EmmyLua Analyzer Rust project will be documented in this file.* --- +## [0.15.0] - Unreleased + +### 🔧 Changed +- **Refactor IndexAliasName**: 删除原先的索引别名实现(`-- [IndexAliasName]`), 现在使用`---@[index_alias("name")]` + +### ✨ Added +- **Attribute**: 实现了新的特性`---@attribute`,用于定义附加元数据,内置三个特性: +```lua +--- Deprecated. Receives an optional message parameter. +---@attribute deprecated(message: string?) + +--- Skip partial diagnostics, typically used to optimize diagnostic performance. +--- +--- Receives a parameter, the options are: +--- - `table_field` - Skip diagnostic for `table` fields +---@attribute skip_diagnostic(code: string) + +--- Index field alias, will be displayed in `hint` and `completion`. +--- +--- Receives a string parameter for the alias name. +---@attribute index_alias(name: string) +``` + +使用语法为 `---@[attribute_name_1(arg...), attribute_name_2(arg...), ...]`, 可以同时使用多个特性, 示例: +```lua +---@class A +---@[deprecated] # 如果特性可以省略参数, 则可以省略`()` +---@field b string # b 此时被标记为弃用 +---@[index_alias("b")] +---@field [1] string # 此时在提示和补全中会显示为 `b` +``` ## [0.15.0] - 2025-10-10 From c130dc982140dd7ab285f3fdd3dca04fcdbfa92e Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 08:05:09 +0800 Subject: [PATCH 12/30] add `attribute` semantic_token --- .../handlers/semantic_token/build_semantic_tokens.rs | 11 +++++++++++ crates/emmylua_parser/src/syntax/node/mod.rs | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index 624ca97ba..b487d33d1 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -783,6 +783,17 @@ fn build_node_semantic_token( fun_string_highlight(builder, semantic_model, call_expr, &string_token); } } + LuaAst::LuaDocTagAttributeUse(tag_use) => { + // 给 `@[]` 染色, @已经染色过了 + tag_use + .token_by_kind(LuaTokenKind::TkDocAttribute) + .map(|token| { + builder.push(token.syntax(), SemanticTokenType::KEYWORD); + }); + tag_use.syntax().last_token().map(|token| { + builder.push(&token, SemanticTokenType::KEYWORD); + }); + } _ => {} } diff --git a/crates/emmylua_parser/src/syntax/node/mod.rs b/crates/emmylua_parser/src/syntax/node/mod.rs index 48332698b..f72bc2653 100644 --- a/crates/emmylua_parser/src/syntax/node/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/mod.rs @@ -88,8 +88,9 @@ pub enum LuaAst { LuaDocTagAs(LuaDocTagAs), LuaDocTagReturnCast(LuaDocTagReturnCast), LuaDocTagExport(LuaDocTagExport), - LuaDocTagAttribute(LuaDocTagAttribute), LuaDocTagLanguage(LuaDocTagLanguage), + LuaDocTagAttribute(LuaDocTagAttribute), + LuaDocTagAttributeUse(LuaDocTagAttributeUse), // doc description LuaDocDescription(LuaDocDescription), @@ -178,6 +179,7 @@ impl LuaAstNode for LuaAst { LuaAst::LuaDocTagReturnCast(node) => node.syntax(), LuaAst::LuaDocTagExport(node) => node.syntax(), LuaAst::LuaDocTagAttribute(node) => node.syntax(), + LuaAst::LuaDocTagAttributeUse(node) => node.syntax(), LuaAst::LuaDocTagLanguage(node) => node.syntax(), LuaAst::LuaDocDescription(node) => node.syntax(), LuaAst::LuaDocNameType(node) => node.syntax(), @@ -289,6 +291,7 @@ impl LuaAstNode for LuaAst { | LuaSyntaxKind::TypeGeneric | LuaSyntaxKind::TypeStringTemplate | LuaSyntaxKind::TypeMultiLineUnion + | LuaSyntaxKind::DocAttributeUse ) } @@ -454,6 +457,9 @@ impl LuaAstNode for LuaAst { LuaSyntaxKind::TypeMultiLineUnion => { LuaDocMultiLineUnionType::cast(syntax).map(LuaAst::LuaDocMultiLineUnionType) } + LuaSyntaxKind::DocTagAttributeUse => { + LuaDocTagAttributeUse::cast(syntax).map(LuaAst::LuaDocTagAttributeUse) + } _ => None, } } From abd02071669ebcaf4457f7ac499ba462c282b609 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 25 Sep 2025 17:09:09 +0800 Subject: [PATCH 13/30] update `attribute` ast --- .../handlers/completion/providers/doc_type_provider.rs | 10 +++++----- .../handlers/semantic_token/build_semantic_tokens.rs | 2 +- crates/emmylua_parser/src/grammar/doc/tag.rs | 2 +- crates/emmylua_parser/src/grammar/doc/test.rs | 2 +- crates/emmylua_parser/src/kind/lua_token_kind.rs | 2 +- crates/emmylua_parser/src/lexer/lua_doc_lexer.rs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs index e4bfc9225..00ae92d40 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs @@ -46,7 +46,7 @@ pub fn complete_types_by_prefix( continue; } match completion_type { - CompletionType::Attribute => { + CompletionType::AttributeUse => { if let Some(decl_id) = type_decl { let type_decl = builder .semantic_model @@ -79,7 +79,7 @@ pub fn complete_types_by_prefix( pub enum CompletionType { Type, - Attribute, + AttributeUse, } fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option { @@ -88,7 +88,7 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option() { - return Some(CompletionType::Attribute); + return Some(CompletionType::AttributeUse); } return Some(CompletionType::Type); } @@ -128,8 +128,8 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option { - return Some(CompletionType::Attribute); + LuaTokenKind::TkDocAttributeUse => { + return Some(CompletionType::AttributeUse); } _ => None, } diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index b487d33d1..bca7e20e2 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -786,7 +786,7 @@ fn build_node_semantic_token( LuaAst::LuaDocTagAttributeUse(tag_use) => { // 给 `@[]` 染色, @已经染色过了 tag_use - .token_by_kind(LuaTokenKind::TkDocAttribute) + .token_by_kind(LuaTokenKind::TkDocAttributeUse) .map(|token| { builder.push(token.syntax(), SemanticTokenType::KEYWORD); }); diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 51cb01302..66453d56a 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -58,7 +58,7 @@ fn parse_tag_detail(p: &mut LuaDocParser) -> DocParseResult { LuaTokenKind::TkTagExport => parse_tag_export(p), LuaTokenKind::TkLanguage => parse_tag_language(p), LuaTokenKind::TkTagAttribute => parse_tag_attribute(p), - LuaTokenKind::TkDocAttribute => parse_attribute_use(p), + LuaTokenKind::TkDocAttributeUse => parse_attribute_use(p), // simple tag LuaTokenKind::TkTagVisibility => parse_tag_simple(p, LuaSyntaxKind::DocTagVisibility), diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 44f402ab4..b1add228a 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2911,7 +2911,7 @@ Syntax(Chunk)@0..105 Token(TkWhitespace)@57..65 " " Token(TkDocStart)@65..69 "---@" Syntax(DocTagAttributeUse)@69..96 - Token(TkDocAttribute)@69..70 "[" + Token(TkDocAttributeUse)@69..70 "[" Syntax(DocAttributeUse)@70..74 Syntax(TypeName)@70..74 Token(TkName)@70..74 "Skip" diff --git a/crates/emmylua_parser/src/kind/lua_token_kind.rs b/crates/emmylua_parser/src/kind/lua_token_kind.rs index f84d237a5..39b21059a 100644 --- a/crates/emmylua_parser/src/kind/lua_token_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_token_kind.rs @@ -158,7 +158,7 @@ pub enum LuaTokenKind { TkDocRegion, // region TkDocEndRegion, // endregion TkDocSeeContent, // see content - TkDocAttribute, // '@[', used for attribute usage + TkDocAttributeUse, // '@[', used for attribute usage } impl fmt::Display for LuaTokenKind { diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index d913d47b9..2b05efa55 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -147,7 +147,7 @@ impl LuaDocLexer<'_> { '[' => { reader.bump(); self.state = LuaDocLexerState::AttributeUse; - LuaTokenKind::TkDocAttribute + LuaTokenKind::TkDocAttributeUse } ch if is_name_start(ch) => { reader.bump(); @@ -288,7 +288,7 @@ impl LuaDocLexer<'_> { // 需要检查是否在使用 Attribute 语法 if reader.current_char() == '[' { reader.bump(); - LuaTokenKind::TkDocAttribute + LuaTokenKind::TkDocAttributeUse } else { reader.eat_while(|_| true); LuaTokenKind::TkDocDetail From 27082f557392db2d20c8483e18b9358081f65dad Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 26 Sep 2025 14:00:39 +0800 Subject: [PATCH 14/30] Change `DocAttribute` to `DocTypeFlag` --- .../src/compilation/analyzer/decl/docs.rs | 8 +++--- .../providers/doc_name_token_provider.rs | 26 +++++++++---------- crates/emmylua_parser/src/grammar/doc/tag.rs | 2 +- .../src/kind/lua_syntax_kind.rs | 2 +- .../emmylua_parser/src/syntax/node/doc/mod.rs | 8 +++--- .../emmylua_parser/src/syntax/node/doc/tag.rs | 8 +++--- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs index f64eaacd9..5f18e9b65 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs @@ -1,7 +1,7 @@ use emmylua_parser::{ - LuaAstNode, LuaAstToken, LuaComment, LuaDocAttribute, LuaDocTag, LuaDocTagAlias, - LuaDocTagAttribute, LuaDocTagClass, LuaDocTagEnum, LuaDocTagMeta, LuaDocTagNamespace, - LuaDocTagUsing, + LuaAstNode, LuaAstToken, LuaComment, LuaDocTag, LuaDocTagAlias, LuaDocTagAttribute, + LuaDocTagClass, LuaDocTagEnum, LuaDocTagMeta, LuaDocTagNamespace, LuaDocTagUsing, + LuaDocTypeFlag, }; use flagset::FlagSet; use rowan::TextRange; @@ -25,7 +25,7 @@ pub fn analyze_doc_tag_class(analyzer: &mut DeclAnalyzer, class: LuaDocTagClass) fn get_attrib_value( analyzer: &mut DeclAnalyzer, - attrib: Option, + attrib: Option, ) -> FlagSet { let mut attr: FlagSet = if analyzer.is_meta { LuaTypeAttribute::Meta.into() diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs index 023783342..87c5f92f1 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use emmylua_code_analysis::{DiagnosticCode, LuaTypeAttribute}; use emmylua_parser::{ - LuaAst, LuaAstNode, LuaClosureExpr, LuaComment, LuaDocAttribute, LuaDocTag, LuaSyntaxKind, + LuaAst, LuaAstNode, LuaClosureExpr, LuaComment, LuaDocTag, LuaDocTypeFlag, LuaSyntaxKind, LuaSyntaxToken, LuaTokenKind, }; use lsp_types::CompletionItem; @@ -29,7 +29,7 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { DocCompletionExpected::DiagnosticCode => { add_tag_diagnostic_code_completion(builder); } - DocCompletionExpected::ClassAttr(node) => { + DocCompletionExpected::ClassFlag(node) => { add_tag_class_attr_completion(builder, node); } DocCompletionExpected::Namespace => { @@ -84,9 +84,9 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option { Some(DocCompletionExpected::DiagnosticCode) } - LuaSyntaxKind::DocAttribute => { - let attr = LuaDocAttribute::cast(parent.clone())?; - Some(DocCompletionExpected::ClassAttr(attr)) + LuaSyntaxKind::DocTypeFlag => { + let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, } @@ -105,9 +105,9 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option Some(DocCompletionExpected::DiagnosticCode), - LuaSyntaxKind::DocAttribute => { - let attr = LuaDocAttribute::cast(parent.clone())?; - Some(DocCompletionExpected::ClassAttr(attr)) + LuaSyntaxKind::DocTypeFlag => { + let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, } @@ -115,9 +115,9 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option { let parent = trigger_token.parent()?; match parent.kind().into() { - LuaSyntaxKind::DocAttribute => { - let attr = LuaDocAttribute::cast(parent.clone())?; - Some(DocCompletionExpected::ClassAttr(attr)) + LuaSyntaxKind::DocTypeFlag => { + let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, } @@ -132,7 +132,7 @@ enum DocCompletionExpected { Cast, DiagnosticAction, DiagnosticCode, - ClassAttr(LuaDocAttribute), + ClassFlag(LuaDocTypeFlag), Namespace, Using, Export, @@ -230,7 +230,7 @@ fn add_tag_diagnostic_code_completion(builder: &mut CompletionBuilder) { fn add_tag_class_attr_completion( builder: &mut CompletionBuilder, - node: LuaDocAttribute, + node: LuaDocTypeFlag, ) -> Option<()> { let mut attributes = vec![(LuaTypeAttribute::Partial, "partial")]; diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 66453d56a..0551f6d12 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -107,7 +107,7 @@ fn parse_tag_class(p: &mut LuaDocParser) -> DocParseResult { // (partial, global, local) fn parse_doc_attribute(p: &mut LuaDocParser) -> DocParseResult { - let m = p.mark(LuaSyntaxKind::DocAttribute); + let m = p.mark(LuaSyntaxKind::DocTypeFlag); p.bump(); expect_token(p, LuaTokenKind::TkName)?; while p.current_token() == LuaTokenKind::TkComma { diff --git a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs index 95ffa6628..a6d646a5c 100644 --- a/crates/emmylua_parser/src/kind/lua_syntax_kind.rs +++ b/crates/emmylua_parser/src/kind/lua_syntax_kind.rs @@ -127,7 +127,7 @@ pub enum LuaSyntaxKind { DocGenericDeclareList, DocDiagnosticNameList, DocTypeList, - DocAttribute, // (partial, global, local, ...) + DocTypeFlag, // (partial, global, local, ...) DocAttributeUse, // use. attribute in @[attribute1, attribute2, ...] DocAttributeCallArgList, // use. argument list in @[attribute_name(arg1, arg2, ...)] DocOpType, // +, -, +? diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index 37aed966d..49082e5bc 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -408,11 +408,11 @@ pub enum LuaDocObjectFieldKey { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct LuaDocAttribute { +pub struct LuaDocTypeFlag { syntax: LuaSyntaxNode, } -impl LuaAstNode for LuaDocAttribute { +impl LuaAstNode for LuaDocTypeFlag { fn syntax(&self) -> &LuaSyntaxNode { &self.syntax } @@ -421,7 +421,7 @@ impl LuaAstNode for LuaDocAttribute { where Self: Sized, { - kind == LuaSyntaxKind::DocAttribute + kind == LuaSyntaxKind::DocTypeFlag } fn cast(syntax: LuaSyntaxNode) -> Option @@ -436,7 +436,7 @@ impl LuaAstNode for LuaDocAttribute { } } -impl LuaDocAttribute { +impl LuaDocTypeFlag { pub fn get_attrib_tokens(&self) -> LuaAstTokenChildren { self.tokens() } diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index 4dfa3302b..275ab6501 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -8,7 +8,7 @@ use crate::{ }; use super::{ - LuaDocAttribute, LuaDocGenericDeclList, LuaDocOpType, LuaDocType, LuaDocTypeList, + LuaDocGenericDeclList, LuaDocOpType, LuaDocType, LuaDocTypeFlag, LuaDocTypeList, description::LuaDocDetailOwner, }; @@ -264,7 +264,7 @@ impl LuaDocTagClass { self.child() } - pub fn get_attrib(&self) -> Option { + pub fn get_attrib(&self) -> Option { self.child() } } @@ -313,7 +313,7 @@ impl LuaDocTagEnum { self.child() } - pub fn get_attrib(&self) -> Option { + pub fn get_attrib(&self) -> Option { self.child() } } @@ -691,7 +691,7 @@ impl LuaDocTagField { self.token() } - pub fn get_attrib(&self) -> Option { + pub fn get_attrib(&self) -> Option { self.child() } } From ef9a9372ecbac1d1918fb0cf4863e767d648f04b Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 26 Sep 2025 14:33:33 +0800 Subject: [PATCH 15/30] update `attribute` ast, allow embedding of @param and @generic --- crates/emmylua_parser/src/grammar/doc/tag.rs | 36 ++++++++--- crates/emmylua_parser/src/grammar/doc/test.rs | 61 +++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 0551f6d12..68269edd6 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -58,7 +58,7 @@ fn parse_tag_detail(p: &mut LuaDocParser) -> DocParseResult { LuaTokenKind::TkTagExport => parse_tag_export(p), LuaTokenKind::TkLanguage => parse_tag_language(p), LuaTokenKind::TkTagAttribute => parse_tag_attribute(p), - LuaTokenKind::TkDocAttributeUse => parse_attribute_use(p), + LuaTokenKind::TkDocAttributeUse => parse_tag_attribute_use(p, true), // simple tag LuaTokenKind::TkTagVisibility => parse_tag_simple(p, LuaSyntaxKind::DocTagVisibility), @@ -86,7 +86,7 @@ fn parse_tag_class(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagClass); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_doc_attribute(p)?; + parse_doc_type_flag(p)?; } expect_token(p, LuaTokenKind::TkName)?; @@ -106,7 +106,7 @@ fn parse_tag_class(p: &mut LuaDocParser) -> DocParseResult { } // (partial, global, local) -fn parse_doc_attribute(p: &mut LuaDocParser) -> DocParseResult { +fn parse_doc_type_flag(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTypeFlag); p.bump(); expect_token(p, LuaTokenKind::TkName)?; @@ -142,6 +142,10 @@ fn parse_generic_decl_list(p: &mut LuaDocParser, allow_angle_brackets: bool) -> // A ... : type fn parse_generic_param(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocGenericParameter); + // 允许泛型附带特性 + if p.current_token() == LuaTokenKind::TkLeftBracket { + parse_tag_attribute_use(p, false)?; + } expect_token(p, LuaTokenKind::TkName)?; if p.current_token() == LuaTokenKind::TkDots { p.bump(); @@ -160,7 +164,7 @@ fn parse_tag_enum(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagEnum); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_doc_attribute(p)?; + parse_doc_type_flag(p)?; } expect_token(p, LuaTokenKind::TkName)?; @@ -247,7 +251,7 @@ fn parse_tag_field(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagField); p.bump(); if p.current_token() == LuaTokenKind::TkLeftParen { - parse_doc_attribute(p)?; + parse_doc_type_flag(p)?; } p.set_state(LuaDocLexerState::Normal); @@ -307,6 +311,9 @@ fn parse_tag_param(p: &mut LuaDocParser) -> DocParseResult { p.set_state(LuaDocLexerState::Normal); let m = p.mark(LuaSyntaxKind::DocTagParam); p.bump(); + if p.current_token() == LuaTokenKind::TkLeftBracket { + parse_tag_attribute_use(p, false)?; + } if matches!( p.current_token(), LuaTokenKind::TkName | LuaTokenKind::TkDots @@ -682,8 +689,11 @@ fn parse_type_attribute(p: &mut LuaDocParser) -> DocParseResult { Ok(m.complete(p)) } -// ---@[attribute_name(params)] or ---@[attribute_name] -pub fn parse_attribute_use(p: &mut LuaDocParser) -> DocParseResult { +// ---@[a(arg1, arg2, ...)] +// ---@[a] +// ---@[a, b, ...] +// ---@generic [attribute] T +pub fn parse_tag_attribute_use(p: &mut LuaDocParser, allow_description: bool) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagAttributeUse); p.bump(); // consume '[' @@ -699,15 +709,21 @@ pub fn parse_attribute_use(p: &mut LuaDocParser) -> DocParseResult { expect_token(p, LuaTokenKind::TkRightBracket)?; // 属性使用解析完成后, 重置状态 - p.set_state(LuaDocLexerState::Description); - + if allow_description { + p.set_state(LuaDocLexerState::Description); + parse_description(p); + } else { + p.set_state(LuaDocLexerState::Normal); + } Ok(m.complete(p)) } -// ---@[attribute1, attribute2, ...] +// attribute +// attribute(arg1, arg2, ...) fn parse_doc_attribute_use(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocAttributeUse); + // attribute 被视为类型 parse_type(p)?; // 解析参数列表, 允许没有参数的特性在使用时省略括号 diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index b1add228a..7ceb7ad9c 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2935,4 +2935,65 @@ Syntax(Chunk)@0..105 "#; assert_ast_eq!(code, result); } + + #[test] + fn test_attribute_generic_param() { + let code = r#" + ---@generic [attribute] T, [attribute] R + ---@param [attribute] a number + "#; + // print_ast(code); + let result = r#" +Syntax(Chunk)@0..97 + Syntax(Block)@0..97 + Token(TkEndOfLine)@0..1 "\n" + Token(TkWhitespace)@1..9 " " + Syntax(Comment)@9..88 + Token(TkDocStart)@9..13 "---@" + Syntax(DocTagGeneric)@13..49 + Token(TkTagGeneric)@13..20 "generic" + Token(TkWhitespace)@20..21 " " + Syntax(DocGenericDeclareList)@21..49 + Syntax(DocGenericParameter)@21..34 + Syntax(DocTagAttributeUse)@21..32 + Token(TkLeftBracket)@21..22 "[" + Syntax(DocAttributeUse)@22..31 + Syntax(TypeName)@22..31 + Token(TkName)@22..31 "attribute" + Token(TkRightBracket)@31..32 "]" + Token(TkWhitespace)@32..33 " " + Token(TkName)@33..34 "T" + Token(TkComma)@34..35 "," + Token(TkWhitespace)@35..36 " " + Syntax(DocGenericParameter)@36..49 + Syntax(DocTagAttributeUse)@36..47 + Token(TkLeftBracket)@36..37 "[" + Syntax(DocAttributeUse)@37..46 + Syntax(TypeName)@37..46 + Token(TkName)@37..46 "attribute" + Token(TkRightBracket)@46..47 "]" + Token(TkWhitespace)@47..48 " " + Token(TkName)@48..49 "R" + Token(TkEndOfLine)@49..50 "\n" + Token(TkWhitespace)@50..58 " " + Token(TkDocStart)@58..62 "---@" + Syntax(DocTagParam)@62..88 + Token(TkTagParam)@62..67 "param" + Token(TkWhitespace)@67..68 " " + Syntax(DocTagAttributeUse)@68..79 + Token(TkLeftBracket)@68..69 "[" + Syntax(DocAttributeUse)@69..78 + Syntax(TypeName)@69..78 + Token(TkName)@69..78 "attribute" + Token(TkRightBracket)@78..79 "]" + Token(TkWhitespace)@79..80 " " + Token(TkName)@80..81 "a" + Token(TkWhitespace)@81..82 " " + Syntax(TypeName)@82..88 + Token(TkName)@82..88 "number" + Token(TkEndOfLine)@88..89 "\n" + Token(TkWhitespace)@89..97 " " + "#; + assert_ast_eq!(code, result); + } } From fafa7fe01c59de13194d9a9af1b4700e31d2f6cb Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sat, 27 Sep 2025 06:48:58 +0800 Subject: [PATCH 16/30] Add `attribute` to function generic parameters --- .../analyzer/doc/attribute_tags.rs | 32 +++++++++++++------ .../compilation/analyzer/doc/type_def_tags.rs | 24 ++++++++++++-- .../src/compilation/test/attribute_test.rs | 12 +++++++ .../src/db_index/signature/mod.rs | 4 +-- .../src/db_index/signature/signature.rs | 25 +++++++++++++-- .../src/db_index/type/humanize_type.rs | 2 +- .../generic/generic_constraint_mismatch.rs | 10 ++++-- .../src/semantic/generic/mod.rs | 4 +-- .../src/json_generator/export.rs | 7 ++-- .../completion/providers/function_provider.rs | 7 +++- .../emmylua_parser/src/syntax/node/doc/mod.rs | 4 +++ 11 files changed, 104 insertions(+), 27 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs index 847651517..4b4e479ae 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -15,17 +15,33 @@ use crate::{ pub fn analyze_tag_attribute_use( analyzer: &mut DocAnalyzer, - attribute_use: LuaDocTagAttributeUse, + tag_use: LuaDocTagAttributeUse, ) -> Option<()> { - let owner = attribute_use_get_owner(analyzer, &attribute_use); + let owner = attribute_use_get_owner(analyzer, &tag_use); let owner_id = match get_owner_id(analyzer, owner, true) { Some(id) => id, None => { - report_orphan_tag(analyzer, &attribute_use); + report_orphan_tag(analyzer, &tag_use); return None; } }; - let attribute_uses = attribute_use.get_attribute_uses(); + let attribute_uses = infer_attribute_uses(analyzer, tag_use)?; + for attribute_use in attribute_uses { + analyzer.db.get_property_index_mut().add_attribute_use( + analyzer.file_id, + owner_id.clone(), + attribute_use, + ); + } + Some(()) +} + +pub fn infer_attribute_uses( + analyzer: &mut DocAnalyzer, + tag_use: LuaDocTagAttributeUse, +) -> Option> { + let attribute_uses = tag_use.get_attribute_uses(); + let mut result = Vec::new(); for attribute_use in attribute_uses { let mut params = Vec::new(); if let Some(attribute_call_arg_list) = attribute_use.get_arg_list() { @@ -36,14 +52,10 @@ pub fn analyze_tag_attribute_use( } let attribute_type = infer_type(analyzer, LuaDocType::Name(attribute_use.get_type()?)); if let LuaType::Ref(type_id) = attribute_type { - analyzer.db.get_property_index_mut().add_attribute_use( - analyzer.file_id, - owner_id.clone(), - LuaAttributeUse::new(type_id, params), - ); + result.push(LuaAttributeUse::new(type_id, params)); } } - Some(()) + Some(result) } fn infer_attribute_arg_type(expr: LuaLiteralExpr) -> LuaType { diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs index 55a2091ec..022d85b35 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs @@ -11,12 +11,16 @@ use super::{ DocAnalyzer, infer_type::infer_type, preprocess_description, tags::find_owner_closure, }; use crate::GenericParam; +use crate::compilation::analyzer::doc::attribute_tags::infer_attribute_uses; use crate::compilation::analyzer::doc::tags::report_orphan_tag; use crate::{ LuaTypeCache, LuaTypeDeclId, compilation::analyzer::common::bind_type, - db_index::{LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaSignatureId, LuaType}, + db_index::{ + LuaDeclId, LuaGenericParamInfo, LuaMemberId, LuaSemanticDeclId, LuaSignatureId, LuaType, + }, }; +use std::sync::Arc; pub fn analyze_class(analyzer: &mut DocAnalyzer, tag: LuaDocTagClass) -> Option<()> { let file_id = analyzer.file_id; @@ -348,7 +352,6 @@ pub fn analyze_func_generic(analyzer: &mut DocAnalyzer, tag: LuaDocTagGeneric) - } else { continue; }; - let type_ref = param .get_type() .map(|type_ref| infer_type(analyzer, type_ref)); @@ -358,7 +361,22 @@ pub fn analyze_func_generic(analyzer: &mut DocAnalyzer, tag: LuaDocTagGeneric) - type_ref.clone(), false, )); - param_info.push((name, type_ref)); + + let attributes = { + let mut attributes = None; + if let Some(attribute_use) = param.get_tag_attribute_use() { + if let Some(attribute_uses) = infer_attribute_uses(analyzer, attribute_use) { + for attribute_use in attribute_uses { + attributes = Some(attribute_use); + } + } + } + attributes + }; + + param_info.push(Arc::new(LuaGenericParamInfo::new( + name, type_ref, attributes, + ))); } } diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index cc5478974..c7f5dd576 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -45,4 +45,16 @@ mod test { // "#, // ); } + + #[test] + fn test_generic_attribute() { + let mut ws = VirtualWorkspace::new(); + ws.def( + r#" + ---@generic [attribute] T + local function f() + end + "#, + ); + } } diff --git a/crates/emmylua_code_analysis/src/db_index/signature/mod.rs b/crates/emmylua_code_analysis/src/db_index/signature/mod.rs index 223c9cbc3..7c496430b 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/mod.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/mod.rs @@ -6,8 +6,8 @@ use std::collections::{HashMap, HashSet}; pub use async_state::AsyncState; pub use signature::{ - LuaDocParamInfo, LuaDocReturnInfo, LuaNoDiscard, LuaSignature, LuaSignatureId, - SignatureReturnStatus, + LuaDocParamInfo, LuaDocReturnInfo, LuaGenericParamInfo, LuaNoDiscard, LuaSignature, + LuaSignatureId, SignatureReturnStatus, }; use crate::FileId; diff --git a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs index 46b028fd1..8e2a7b6d1 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs @@ -11,11 +11,11 @@ use crate::{ FileId, db_index::{LuaFunctionType, LuaType}, }; -use crate::{SemanticModel, VariadicType}; +use crate::{LuaAttributeUse, SemanticModel, VariadicType}; #[derive(Debug)] pub struct LuaSignature { - pub generic_params: Vec<(String, Option)>, + pub generic_params: Vec>, pub overloads: Vec>, pub param_docs: HashMap, pub params: Vec, @@ -279,3 +279,24 @@ pub enum SignatureReturnStatus { DocResolve, InferResolve, } + +#[derive(Debug, Clone)] +pub struct LuaGenericParamInfo { + pub name: String, + pub type_constraint: Option, + pub attributes: Option, +} + +impl LuaGenericParamInfo { + pub fn new( + name: String, + type_constraint: Option, + attributes: Option, + ) -> Self { + Self { + name, + type_constraint, + attributes, + } + } +} diff --git a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs index 33974104c..80aed9149 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs @@ -713,7 +713,7 @@ fn humanize_signature_type( let generics = signature .generic_params .iter() - .map(|(name, _)| name.to_string()) + .map(|generic_param| generic_param.name.to_string()) .collect::>() .join(", "); diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/generic/generic_constraint_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/generic/generic_constraint_mismatch.rs index c04a0f1c1..b7dc0fae7 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/generic/generic_constraint_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/generic/generic_constraint_mismatch.rs @@ -201,7 +201,11 @@ fn get_extend_type( signature: &LuaSignature, ) -> Option { match tpl_id { - GenericTplId::Func(tpl_id) => signature.generic_params.get(tpl_id as usize)?.1.clone(), + GenericTplId::Func(tpl_id) => signature + .generic_params + .get(tpl_id as usize)? + .type_constraint + .clone(), GenericTplId::Type(tpl_id) => { let prefix_expr = call_expr.get_prefix_expr()?; let semantic_decl = semantic_model.find_decl( @@ -391,10 +395,10 @@ fn try_instantiate_arg_type( .get_db() .get_signature_index() .get(&signature_id)?; - if let Some((_, param_type)) = + if let Some(generic_param) = signature.generic_params.get(tpl_id as usize) { - return param_type.clone(); + return generic_param.type_constraint.clone(); } } _ => return None, diff --git a/crates/emmylua_code_analysis/src/semantic/generic/mod.rs b/crates/emmylua_code_analysis/src/semantic/generic/mod.rs index a75f85d24..23f3a74a5 100644 --- a/crates/emmylua_code_analysis/src/semantic/generic/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/generic/mod.rs @@ -50,10 +50,10 @@ pub fn get_tpl_ref_extend_type( match decl.extra { LuaDeclExtra::Param { signature_id, .. } => { let signature = db.get_signature_index().get(&signature_id)?; - if let Some((_, param_type)) = + if let Some(generic_param) = signature.generic_params.get(tpl_id as usize) { - return param_type.clone(); + return generic_param.type_constraint.clone(); } } _ => return None, diff --git a/crates/emmylua_doc_cli/src/json_generator/export.rs b/crates/emmylua_doc_cli/src/json_generator/export.rs index dbdf6c45b..bc6e5f7aa 100644 --- a/crates/emmylua_doc_cli/src/json_generator/export.rs +++ b/crates/emmylua_doc_cli/src/json_generator/export.rs @@ -260,9 +260,10 @@ fn export_signature( generics: signature .generic_params .iter() - .map(|(name, typ)| TypeVar { - name: name.clone(), - base: typ + .map(|generic_param| TypeVar { + name: generic_param.name.to_string(), + base: generic_param + .type_constraint .as_ref() .map(|typ| render_typ(db, typ, RenderLevel::Simple)), }) diff --git a/crates/emmylua_ls/src/handlers/completion/providers/function_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/function_provider.rs index dde93fe49..fe5795807 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/function_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/function_provider.rs @@ -792,7 +792,12 @@ fn get_tpl_ref_extend_type(builder: &CompletionBuilder, tpl_id: &GenericTplId) - .get(&signature_id)?; let generic_param = signature.generic_params.get(tpl_id.get_idx()); if let Some(generic_param) = generic_param { - return Some(generic_param.1.clone().unwrap_or(LuaType::Any)); + return Some( + generic_param + .type_constraint + .clone() + .unwrap_or(LuaType::Any), + ); } } None diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index 49082e5bc..dd4b75ca6 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -242,6 +242,10 @@ impl LuaDocGenericDecl { pub fn is_variadic(&self) -> bool { self.token_by_kind(LuaTokenKind::TkDots).is_some() } + + pub fn get_tag_attribute_use(&self) -> Option { + self.child() + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] From b9593ef272e8599c759f5c7d99f47aca7dcd357e Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sat, 27 Sep 2025 22:11:55 +0800 Subject: [PATCH 17/30] Add `attribute` to function parameters and class generics --- .../compilation/analyzer/doc/type_def_tags.rs | 27 +++++++++---------- .../compilation/analyzer/doc/type_ref_tags.rs | 14 ++++++++-- .../analyzer/unresolve/resolve_closure.rs | 2 ++ .../src/compilation/test/attribute_test.rs | 9 ++++++- .../src/db_index/signature/signature.rs | 5 ++-- .../src/db_index/type/generic_param.rs | 11 ++++++-- crates/emmylua_parser/src/grammar/doc/test.rs | 3 +++ .../emmylua_parser/src/syntax/node/doc/tag.rs | 4 +++ 8 files changed, 53 insertions(+), 22 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs index 022d85b35..2b9c6c35f 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs @@ -215,13 +215,17 @@ fn get_generic_params( } else { continue; }; - + let attributes = if let Some(attribute_use) = param.get_tag_attribute_use() { + infer_attribute_uses(analyzer, attribute_use) + } else { + None + }; let type_ref = param .get_type() .map(|type_ref| infer_type(analyzer, type_ref)); let is_variadic = param.is_variadic(); - params_result.push(GenericParam::new(name, type_ref, is_variadic)); + params_result.push(GenericParam::new(name, type_ref, is_variadic, attributes)); } params_result @@ -356,24 +360,17 @@ pub fn analyze_func_generic(analyzer: &mut DocAnalyzer, tag: LuaDocTagGeneric) - .get_type() .map(|type_ref| infer_type(analyzer, type_ref)); + let attributes = if let Some(attribute_use) = param.get_tag_attribute_use() { + infer_attribute_uses(analyzer, attribute_use) + } else { + None + }; params_result.push(GenericParam::new( SmolStr::new(name.as_str()), type_ref.clone(), false, + attributes.clone(), )); - - let attributes = { - let mut attributes = None; - if let Some(attribute_use) = param.get_tag_attribute_use() { - if let Some(attribute_uses) = infer_attribute_uses(analyzer, attribute_use) { - for attribute_use in attribute_uses { - attributes = Some(attribute_use); - } - } - } - attributes - }; - param_info.push(Arc::new(LuaGenericParamInfo::new( name, type_ref, attributes, ))); diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs index 44775adfd..5933dee88 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs @@ -11,8 +11,9 @@ use super::{ preprocess_description, tags::{find_owner_closure, get_owner_id_or_report}, }; -use crate::compilation::analyzer::doc::tags::{ - find_owner_closure_or_report, get_owner_id, report_orphan_tag, +use crate::compilation::analyzer::doc::{ + attribute_tags::infer_attribute_uses, + tags::{find_owner_closure_or_report, get_owner_id, report_orphan_tag}, }; use crate::{ InFiled, InferFailReason, LuaOperatorMetaMethod, LuaTypeCache, LuaTypeOwner, OperatorFunction, @@ -186,18 +187,27 @@ pub fn analyze_param(analyzer: &mut DocAnalyzer, tag: LuaDocTagParam) -> Option< // bind type ref to signature and param if let Some(closure) = find_owner_closure(analyzer) { let id = LuaSignatureId::from_closure(analyzer.file_id, &closure); + // 绑定`attribute`标记 + let attributes = if let Some(attribute_use) = tag.get_tag_attribute_use() { + infer_attribute_uses(analyzer, attribute_use) + } else { + None + }; + let signature = analyzer.db.get_signature_index_mut().get_or_create(id); let param_info = LuaDocParamInfo { name: name.clone(), type_ref: type_ref.clone(), nullable, description, + attributes, }; let idx = signature.find_param_idx(&name)?; signature.param_docs.insert(idx, param_info); } else if let Some(LuaAst::LuaForRangeStat(for_range)) = analyzer.comment.get_owner() { + // for in 支持 @param 语法 for it_name_token in for_range.get_var_name_list() { let it_name = it_name_token.get_name_text(); if it_name == name { diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs index 9bc2d4d3f..415744731 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs @@ -94,6 +94,7 @@ pub fn try_resolve_call_closure_params( type_ref: type_ref.clone().unwrap_or(LuaType::Any), description: None, nullable: false, + attributes: None, }, ); } @@ -467,6 +468,7 @@ fn resolve_doc_function( type_ref: param.1.clone().unwrap_or(LuaType::Any), description: None, nullable: false, + attributes: None, }, ); } diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index c7f5dd576..bc657efb0 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -47,8 +47,15 @@ mod test { } #[test] - fn test_generic_attribute() { + fn test_attribute_attach() { let mut ws = VirtualWorkspace::new(); + // ws.def( + // r#" + // ---@generic [attribute] T + // local function f() + // end + // "#, + // ); ws.def( r#" ---@generic [attribute] T diff --git a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs index 8e2a7b6d1..dfe7850b4 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs @@ -182,6 +182,7 @@ pub struct LuaDocParamInfo { pub type_ref: LuaType, pub nullable: bool, pub description: Option, + pub attributes: Option>, } #[derive(Debug)] @@ -284,14 +285,14 @@ pub enum SignatureReturnStatus { pub struct LuaGenericParamInfo { pub name: String, pub type_constraint: Option, - pub attributes: Option, + pub attributes: Option>, } impl LuaGenericParamInfo { pub fn new( name: String, type_constraint: Option, - attributes: Option, + attributes: Option>, ) -> Self { Self { name, diff --git a/crates/emmylua_code_analysis/src/db_index/type/generic_param.rs b/crates/emmylua_code_analysis/src/db_index/type/generic_param.rs index 153e5258d..a6282537c 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/generic_param.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/generic_param.rs @@ -1,20 +1,27 @@ use smol_str::SmolStr; -use crate::LuaType; +use crate::{LuaAttributeUse, LuaType}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericParam { pub name: SmolStr, pub type_constraint: Option, pub is_variadic: bool, + pub attributes: Option>, } impl GenericParam { - pub fn new(name: SmolStr, type_constraint: Option, is_variadic: bool) -> Self { + pub fn new( + name: SmolStr, + type_constraint: Option, + is_variadic: bool, + attributes: Option>, + ) -> Self { Self { name, type_constraint, is_variadic, + attributes, } } } diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 7ceb7ad9c..dd54adb06 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2943,6 +2943,9 @@ Syntax(Chunk)@0..105 ---@param [attribute] a number "#; // print_ast(code); + // print_ast(r#" + // ---@class A<[attribute] T, [attribute] R> + // "#); let result = r#" Syntax(Chunk)@0..97 Syntax(Block)@0..97 diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index 275ab6501..25b10a5e2 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -488,6 +488,10 @@ impl LuaDocTagParam { pub fn get_type(&self) -> Option { self.child() } + + pub fn get_tag_attribute_use(&self) -> Option { + self.child() + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] From 10c82392e7c1499e378b0e360ab51aa319eedc4b Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 28 Sep 2025 04:37:30 +0800 Subject: [PATCH 18/30] updated the `attribute` AST to allow usage in `@return` --- .../compilation/analyzer/doc/type_ref_tags.rs | 10 ++++- .../src/compilation/analyzer/lua/closure.rs | 2 + .../analyzer/unresolve/check_reason.rs | 1 + .../analyzer/unresolve/resolve_closure.rs | 2 + .../src/db_index/signature/signature.rs | 1 + .../semantic_token/build_semantic_tokens.rs | 4 +- crates/emmylua_parser/src/grammar/doc/tag.rs | 7 ++++ crates/emmylua_parser/src/grammar/doc/test.rs | 41 +++++++++++++++---- .../emmylua_parser/src/syntax/node/doc/tag.rs | 19 +++++++-- 9 files changed, 72 insertions(+), 15 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs index 5933dee88..a8f25db3b 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs @@ -234,15 +234,21 @@ pub fn analyze_return(analyzer: &mut DocAnalyzer, tag: LuaDocTagReturn) -> Optio if let Some(closure) = find_owner_closure_or_report(analyzer, &tag) { let signature_id = LuaSignatureId::from_closure(analyzer.file_id, &closure); - let returns = tag.get_type_and_name_list(); - for (doc_type, name_token) in returns { + let returns = tag.get_info_list(); + for (doc_type, name_token, attribute_use_tag) in returns { let name = name_token.map(|name| name.get_name_text().to_string()); let type_ref = infer_type(analyzer, doc_type); + let attributes = if let Some(attribute) = attribute_use_tag { + infer_attribute_uses(analyzer, attribute) + } else { + None + }; let return_info = LuaDocReturnInfo { name, type_ref, description: description.clone(), + attributes, }; let signature = analyzer diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/closure.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/closure.rs index e041fd822..d04ab89ca 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/closure.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/closure.rs @@ -151,6 +151,7 @@ fn analyze_return( type_ref: LuaType::Unknown, description: None, name: None, + attributes: None, }] } Err(reason) => { @@ -236,6 +237,7 @@ pub fn analyze_return_point( type_ref: return_type, description: None, name: None, + attributes: None, }]) } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs index a90176112..452e53e72 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs @@ -97,6 +97,7 @@ pub fn resolve_as_any(db: &mut DbIndex, reason: &InferFailReason, loop_count: us name: None, type_ref: LuaType::Any, description: None, + attributes: None, }]; signature.resolve_return = SignatureReturnStatus::InferResolve; } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs index 415744731..7291eeb1c 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve_closure.rs @@ -172,6 +172,7 @@ pub fn try_resolve_closure_return( name: None, type_ref: ret_type.clone(), description: None, + attributes: None, }); signature.resolve_return = SignatureReturnStatus::DocResolve; @@ -482,6 +483,7 @@ fn resolve_doc_function( name: None, type_ref: doc_func.get_ret().clone(), description: None, + attributes: None, }); } diff --git a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs index dfe7850b4..347b6664e 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs @@ -190,6 +190,7 @@ pub struct LuaDocReturnInfo { pub name: Option, pub type_ref: LuaType, pub description: Option, + pub attributes: Option>, } #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index bca7e20e2..fbb44e07a 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -371,8 +371,8 @@ fn build_node_semantic_token( ); } LuaAst::LuaDocTagReturn(doc_return) => { - let type_name_list = doc_return.get_type_and_name_list(); - for (_, name) in type_name_list { + let type_name_list = doc_return.get_info_list(); + for (_, name, _) in type_name_list { if let Some(name) = name { builder.push(name.syntax(), SemanticTokenType::VARIABLE); } diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 68269edd6..c690e2766 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -341,10 +341,14 @@ fn parse_tag_param(p: &mut LuaDocParser) -> DocParseResult { // ---@return number // ---@return number, string // ---@return number , this just compact luals +// ---@return [attribute] number fn parse_tag_return(p: &mut LuaDocParser) -> DocParseResult { p.set_state(LuaDocLexerState::Normal); let m = p.mark(LuaSyntaxKind::DocTagReturn); p.bump(); + if p.current_token() == LuaTokenKind::TkLeftBracket { + parse_tag_attribute_use(p, false)?; + } parse_type(p)?; @@ -352,6 +356,9 @@ fn parse_tag_return(p: &mut LuaDocParser) -> DocParseResult { while p.current_token() == LuaTokenKind::TkComma { p.bump(); + if p.current_token() == LuaTokenKind::TkLeftBracket { + parse_tag_attribute_use(p, false)?; + } parse_type(p)?; if_token_bump(p, LuaTokenKind::TkName); } diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index dd54adb06..2da1eb9fe 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2937,21 +2937,22 @@ Syntax(Chunk)@0..105 } #[test] - fn test_attribute_generic_param() { + fn test_attribute_embedded() { let code = r#" ---@generic [attribute] T, [attribute] R ---@param [attribute] a number + ---@return [attribute] number, [attribute] string "#; - // print_ast(code); + print_ast(code); // print_ast(r#" // ---@class A<[attribute] T, [attribute] R> // "#); let result = r#" -Syntax(Chunk)@0..97 - Syntax(Block)@0..97 +Syntax(Chunk)@0..155 + Syntax(Block)@0..155 Token(TkEndOfLine)@0..1 "\n" Token(TkWhitespace)@1..9 " " - Syntax(Comment)@9..88 + Syntax(Comment)@9..146 Token(TkDocStart)@9..13 "---@" Syntax(DocTagGeneric)@13..49 Token(TkTagGeneric)@13..20 "generic" @@ -2994,8 +2995,34 @@ Syntax(Chunk)@0..97 Token(TkWhitespace)@81..82 " " Syntax(TypeName)@82..88 Token(TkName)@82..88 "number" - Token(TkEndOfLine)@88..89 "\n" - Token(TkWhitespace)@89..97 " " + Token(TkEndOfLine)@88..89 "\n" + Token(TkWhitespace)@89..97 " " + Token(TkDocStart)@97..101 "---@" + Syntax(DocTagReturn)@101..146 + Token(TkTagReturn)@101..107 "return" + Token(TkWhitespace)@107..108 " " + Syntax(DocTagAttributeUse)@108..119 + Token(TkLeftBracket)@108..109 "[" + Syntax(DocAttributeUse)@109..118 + Syntax(TypeName)@109..118 + Token(TkName)@109..118 "attribute" + Token(TkRightBracket)@118..119 "]" + Token(TkWhitespace)@119..120 " " + Syntax(TypeName)@120..126 + Token(TkName)@120..126 "number" + Token(TkComma)@126..127 "," + Token(TkWhitespace)@127..128 " " + Syntax(DocTagAttributeUse)@128..139 + Token(TkLeftBracket)@128..129 "[" + Syntax(DocAttributeUse)@129..138 + Syntax(TypeName)@129..138 + Token(TkName)@129..138 "attribute" + Token(TkRightBracket)@138..139 "]" + Token(TkWhitespace)@139..140 " " + Syntax(TypeName)@140..146 + Token(TkName)@140..146 "string" + Token(TkEndOfLine)@146..147 "\n" + Token(TkWhitespace)@147..155 " " "#; assert_ast_eq!(code, result); } diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index 25b10a5e2..ecb121ddf 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -534,18 +534,26 @@ impl LuaDocTagReturn { self.children() } - pub fn get_type_and_name_list(&self) -> Vec<(LuaDocType, Option)> { + pub fn get_info_list( + &self, + ) -> Vec<( + LuaDocType, + Option, + Option, + )> { let mut result = Vec::new(); let mut current_type = None; let mut current_name = None; + let mut current_attribute = None; for child in self.syntax.children_with_tokens() { match child.kind() { LuaKind::Token(LuaTokenKind::TkComma) => { if let Some(type_) = current_type { - result.push((type_, current_name)); + result.push((type_, current_name, current_attribute)); } current_type = None; current_name = None; + current_attribute = None; } LuaKind::Token(LuaTokenKind::TkName) => { current_name = Some(LuaNameToken::cast(child.into_token().unwrap()).unwrap()); @@ -553,13 +561,16 @@ impl LuaDocTagReturn { k if LuaDocType::can_cast(k.into()) => { current_type = Some(LuaDocType::cast(child.into_node().unwrap()).unwrap()); } - + a if LuaDocTagAttributeUse::can_cast(a.into()) => { + current_attribute = + Some(LuaDocTagAttributeUse::cast(child.into_node().unwrap()).unwrap()); + } _ => {} } } if let Some(type_) = current_type { - result.push((type_, current_name)); + result.push((type_, current_name, current_attribute)); } result From 28b8c5beef44fe400c919cb088d9df11b8b23cc0 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 28 Sep 2025 09:26:26 +0800 Subject: [PATCH 19/30] =?UTF-8?q?=E6=B7=BB=E5=8A=A0`---@attribute=20class?= =?UTF-8?q?=5Fctor`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用于取代配置项`runtime.class_default_call` --- .../resources/std/builtin.lua | 9 + .../src/compilation/analyzer/lua/call.rs | 38 +++++ .../src/compilation/analyzer/lua/mod.rs | 5 +- .../analyzer/unresolve/check_reason.rs | 2 + .../src/compilation/analyzer/unresolve/mod.rs | 27 ++- .../compilation/analyzer/unresolve/resolve.rs | 154 +++++++++++++++++- .../src/compilation/test/attribute_test.rs | 46 ++++++ .../src/db_index/operators/lua_operator.rs | 51 ++++++ .../src/db_index/property/property.rs | 6 +- .../checker/assign_type_mismatch.rs | 2 +- .../src/diagnostic/checker/deprecated.rs | 2 +- .../src/semantic/infer/infer_call/mod.rs | 4 +- .../src/semantic/infer/infer_fail_reason.rs | 2 + .../emmylua_code_analysis/src/semantic/mod.rs | 2 +- .../add_completions/add_member_completion.rs | 2 +- .../src/syntax/node/lua/stat.rs | 1 + 16 files changed, 339 insertions(+), 14 deletions(-) create mode 100644 crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index c40c4d437..6c89b8f73 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -154,3 +154,12 @@ --- --- Receives a string parameter for the alias name. ---@attribute index_alias(name: string) + +--- This attribute must be applied to function parameters, and the function parameter's type must be a string template generic, +--- used to specify the default constructor of a class. +--- +--- Parameters: +--- - `name` - The name of the constructor +--- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true` +--- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true` +---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs new file mode 100644 index 000000000..49929692b --- /dev/null +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs @@ -0,0 +1,38 @@ +use emmylua_parser::LuaCallExpr; + +use crate::{ + InferFailReason, LuaType, + compilation::analyzer::{lua::LuaAnalyzer, unresolve::UnResolveClassCtor}, +}; + +pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Option<()> { + let prefix_expr = call_expr.clone().get_prefix_expr()?; + match analyzer.infer_expr(&prefix_expr) { + Ok(expr_type) => { + let LuaType::Signature(signature_id) = expr_type else { + return Some(()); + }; + let signature = analyzer.db.get_signature_index().get(&signature_id)?; + for (idx, param_info) in signature.param_docs.iter() { + if let Some(ref attrs) = param_info.attributes { + for attr in attrs.iter() { + if attr.id.get_name() == "class_ctor" { + let unresolve = UnResolveClassCtor { + file_id: analyzer.file_id, + call_expr: call_expr.clone(), + signature_id: signature_id, + param_idx: *idx, + }; + analyzer + .context + .add_unresolve(unresolve.into(), InferFailReason::None); + return Some(()); + } + } + } + } + } + Err(_) => {} + } + Some(()) +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs index 6c90885f3..f7054f207 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs @@ -1,3 +1,4 @@ +mod call; mod closure; mod for_range_stat; mod func_body; @@ -22,7 +23,7 @@ use stats::{ use crate::{ Emmyrc, FileId, InferFailReason, - compilation::analyzer::AnalysisPipeline, + compilation::analyzer::{AnalysisPipeline, lua::call::analyze_call}, db_index::{DbIndex, LuaType}, profile::Profile, semantic::infer_expr, @@ -81,6 +82,8 @@ fn analyze_node(analyzer: &mut LuaAnalyzer, node: LuaAst) { LuaAst::LuaCallExpr(call_expr) => { if call_expr.is_setmetatable() { analyze_setmetatable(analyzer, call_expr); + } else { + analyze_call(analyzer, call_expr); } } _ => {} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs index 452e53e72..291ea9db5 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs @@ -18,6 +18,7 @@ pub fn check_reach_reason( match reason { InferFailReason::None | InferFailReason::FieldNotFound + | InferFailReason::UnResolveOperatorCall | InferFailReason::RecursiveInfer => Some(true), InferFailReason::UnResolveDeclType(decl_id) => { let decl = db.get_decl_index().get_decl(decl_id)?; @@ -60,6 +61,7 @@ pub fn resolve_as_any(db: &mut DbIndex, reason: &InferFailReason, loop_count: us match reason { InferFailReason::None | InferFailReason::FieldNotFound + | InferFailReason::UnResolveOperatorCall | InferFailReason::RecursiveInfer => { return Some(()); } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs index 6b8b99dab..eafb971ab 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use crate::{ FileId, InferFailReason, LuaMemberFeature, LuaSemanticDeclId, - compilation::analyzer::AnalysisPipeline, + compilation::analyzer::{AnalysisPipeline, unresolve::resolve::try_resolve_class_ctor}, db_index::{DbIndex, LuaDeclId, LuaMemberId, LuaSignatureId}, profile::Profile, }; @@ -201,6 +201,9 @@ fn try_resolve( UnResolve::TableField(un_resolve_table_field) => { try_resolve_table_field(db, cache, un_resolve_table_field) } + UnResolve::ClassCtor(un_resolve_class_ctor) => { + try_resolve_class_ctor(db, cache, un_resolve_class_ctor) + } }; match resolve_result { @@ -213,6 +216,12 @@ fn try_resolve( retain_unresolve.push((unresolve, InferFailReason::FieldNotFound)); } } + Err(InferFailReason::UnResolveOperatorCall) => { + if !cache.get_config().analysis_phase.is_force() { + retain_unresolve + .push((unresolve, InferFailReason::UnResolveOperatorCall)); + } + } Err(reason) => { if reason != *check_reason { changed = true; @@ -254,6 +263,7 @@ pub enum UnResolve { ClosureParentParams(Box), ModuleRef(Box), TableField(Box), + ClassCtor(Box), } #[allow(dead_code)] @@ -276,6 +286,7 @@ impl UnResolve { } UnResolve::TableField(un_resolve_table_field) => Some(un_resolve_table_field.file_id), UnResolve::ModuleRef(_) => None, + UnResolve::ClassCtor(un_resolve_class_ctor) => Some(un_resolve_class_ctor.file_id), } } } @@ -422,3 +433,17 @@ impl From for UnResolve { UnResolve::TableField(Box::new(un_resolve_table_field)) } } + +#[derive(Debug)] +pub struct UnResolveClassCtor { + pub file_id: FileId, + pub call_expr: LuaCallExpr, + pub signature_id: LuaSignatureId, + pub param_idx: usize, +} + +impl From for UnResolve { + fn from(un_resolve_class_ctor: UnResolveClassCtor) -> Self { + UnResolve::ClassCtor(Box::new(un_resolve_class_ctor)) + } +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 975115cf1..8222bac74 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -1,15 +1,20 @@ use std::ops::Deref; -use emmylua_parser::{LuaAstNode, LuaAstToken, LuaExpr, LuaLocalStat, LuaTableExpr}; +use emmylua_parser::{ + LuaAstNode, LuaAstToken, LuaCallExpr, LuaExpr, LuaIndexExpr, LuaLocalStat, LuaTableExpr, +}; use crate::{ - InFiled, InferFailReason, LuaDeclId, LuaMember, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, - LuaTypeCache, SignatureReturnStatus, TypeOps, + InFiled, InferFailReason, LuaDeclId, LuaMember, LuaMemberId, LuaMemberInfo, LuaMemberKey, + LuaOperator, LuaOperatorMetaMethod, LuaOperatorOwner, LuaSemanticDeclId, LuaTypeCache, + LuaTypeDeclId, OperatorFunction, SignatureReturnStatus, TypeOps, compilation::analyzer::{ common::{add_member, bind_type}, lua::{analyze_return_point, infer_for_range_iter_expr_func}, + unresolve::UnResolveClassCtor, }, db_index::{DbIndex, LuaMemberOwner, LuaType}, + find_members_with_key, semantic::{LuaInferCache, infer_expr}, }; @@ -246,3 +251,146 @@ pub fn try_resolve_module_ref( Ok(()) } + +#[allow(unused)] +pub fn try_resolve_class_ctor( + db: &mut DbIndex, + cache: &mut LuaInferCache, + unresolve_class_ctor: &mut UnResolveClassCtor, +) -> ResolveResult { + let signature = db + .get_signature_index() + .get(&unresolve_class_ctor.signature_id) + .ok_or(InferFailReason::None)?; + let param_info = signature + .get_param_info_by_id(unresolve_class_ctor.param_idx) + .ok_or(InferFailReason::None)?; + let class_ctor_use = param_info + .attributes + .iter() + .flatten() + .find(|attr| attr.id.get_name() == "class_ctor") + .ok_or(InferFailReason::None)?; + let LuaType::DocStringConst(target_signature_name) = + class_ctor_use.args.get(0).ok_or(InferFailReason::None)? + else { + return Err(InferFailReason::None); + }; + let target_type_decl_id = get_class_ctor_target_type( + db, + cache, + ¶m_info.type_ref, + unresolve_class_ctor.call_expr.clone(), + unresolve_class_ctor.param_idx, + ) + .ok_or(InferFailReason::None)?; + let target_type = LuaType::Ref(target_type_decl_id); + let member_key = LuaMemberKey::Name(target_signature_name.deref().clone()); + let members = + find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?; + let ctor_signature_member = members.first().ok_or(InferFailReason::None)?; + let strip_self = { + if let Some(LuaType::DocBooleanConst(strip_self)) = class_ctor_use.args.get(1) { + *strip_self + } else { + true + } + }; + let return_self = { + if let Some(LuaType::DocBooleanConst(return_self)) = class_ctor_use.args.get(2) { + *return_self + } else { + true + } + }; + set_signature_to_default_call(db, cache, ctor_signature_member, strip_self, return_self) + .ok_or(InferFailReason::None)?; + + Ok(()) +} + +fn set_signature_to_default_call( + db: &mut DbIndex, + cache: &mut LuaInferCache, + member_info: &LuaMemberInfo, + strip_self: bool, + return_self: bool, +) -> Option<()> { + let LuaType::Signature(signature_id) = member_info.typ else { + return None; + }; + let Some(LuaSemanticDeclId::Member(member_id)) = member_info.property_owner_id else { + return None; + }; + // 我们仍然需要再做一次判断确定是否来源于`Def`类型 + let root = db + .get_vfs() + .get_syntax_tree(&member_id.file_id)? + .get_red_root(); + let index_expr = LuaIndexExpr::cast(member_id.get_syntax_id().to_node_from_root(&root)?)?; + let prefix_expr = index_expr.get_prefix_expr()?; + let prefix_type = infer_expr(db, cache, prefix_expr.clone()).ok()?; + let LuaType::Def(decl_id) = prefix_type else { + return None; + }; + // 如果已经存在显式的`__call`定义, 则不添加 + let call = db.get_operator_index().get_operators( + &LuaOperatorOwner::Type(decl_id.clone()), + LuaOperatorMetaMethod::Call, + ); + if call.is_some() { + return None; + } + + let operator = LuaOperator::new( + decl_id.into(), + LuaOperatorMetaMethod::Call, + member_id.file_id, + // 必须指向名称, 使用 index_expr 的完整范围不会跳转到函数上 + index_expr.get_name_token()?.syntax().text_range(), + OperatorFunction::DefaultClassCtor { + id: signature_id, + strip_self, + return_self, + }, + ); + db.get_operator_index_mut().add_operator(operator); + Some(()) +} + +fn get_class_ctor_target_type( + db: &DbIndex, + cache: &mut LuaInferCache, + param_type: &LuaType, + call_expr: LuaCallExpr, + call_index: usize, +) -> Option { + match param_type { + LuaType::StrTplRef(str_tpl) => { + let name = { + let arg_expr = call_expr + .get_args_list()? + .get_args() + .nth(call_index)? + .clone(); + let name = infer_expr(db, cache, arg_expr).ok()?; + match name { + LuaType::StringConst(s) => s.to_string(), + _ => return None, + } + }; + + let prefix = str_tpl.get_prefix(); + let suffix = str_tpl.get_suffix(); + let type_decl_id: LuaTypeDeclId = + LuaTypeDeclId::new(format!("{}{}{}", prefix, name, suffix).as_str()); + let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?; + if type_decl.is_class() { + return Some(type_decl_id); + } + } + _ => {} + } + + None +} diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index bc657efb0..5d35d5446 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -64,4 +64,50 @@ mod test { "#, ); } + + #[test] + fn test_class_ctor() { + let mut ws = VirtualWorkspace::new(); + + ws.def_files(vec![ + ( + "init.lua", + r#" + A = meta("A") + "#, + ), + ( + "meta.lua", + r#" + ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + + ---@generic T + ---@param [class_ctor("__init")] name `T` + ---@return T + function meta(name) + end + "#, + ), + ]); + + // ws.def( + // r#" + // ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + + // ---@generic T + // ---@param [class_ctor("__init")] name `T` + // ---@return T + // function meta(name) + // end + // "#, + // ); + // ws.def( + // r#" + // A = meta("A") + + // function A:__init(a) + // end + // "#, + // ); + } } diff --git a/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs b/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs index b13f57930..4ecbbc72c 100644 --- a/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs +++ b/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs @@ -24,6 +24,11 @@ pub enum OperatorFunction { Func(Arc), Signature(LuaSignatureId), DefaultCall(LuaSignatureId), + DefaultClassCtor { + id: LuaSignatureId, + strip_self: bool, + return_self: bool, + }, } impl LuaOperator { @@ -74,6 +79,7 @@ impl LuaOperator { } // 只有 .field 才有`operand`, call 不会有这个 OperatorFunction::DefaultCall(_) => LuaType::Unknown, + OperatorFunction::DefaultClassCtor { .. } => LuaType::Unknown, } } @@ -112,6 +118,23 @@ impl LuaOperator { } } + Ok(LuaType::Any) + } + OperatorFunction::DefaultClassCtor { + id, return_self, .. + } => { + if *return_self { + return Ok(LuaType::SelfInfer); + } + + let signature = db.get_signature_index().get(id); + if let Some(signature) = signature { + let return_type = signature.return_docs.first(); + if let Some(return_type) = return_type { + return Ok(return_type.type_ref.clone()); + } + } + Ok(LuaType::Any) } } @@ -147,6 +170,34 @@ impl LuaOperator { LuaType::Signature(*signature_id) } + OperatorFunction::DefaultClassCtor { + id, + strip_self, + return_self, + } => { + if let Some(signature) = db.get_signature_index().get(id) { + let params = signature.get_type_params(); + let is_colon_define = if *strip_self { + false + } else { + signature.is_colon_define + }; + let return_type = if *return_self { + LuaType::SelfInfer + } else { + signature.get_return_type() + }; + let func_type = LuaFunctionType::new( + signature.async_state, + is_colon_define, + params, + return_type, + ); + return LuaType::DocFunction(Arc::new(func_type)); + } + + LuaType::Signature(*id) + } } } diff --git a/crates/emmylua_code_analysis/src/db_index/property/property.rs b/crates/emmylua_code_analysis/src/db_index/property/property.rs index f1c4251ef..07183d161 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/property.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/property.rs @@ -175,11 +175,11 @@ impl LuaPropertyId { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct LuaAttributeUse { pub id: LuaTypeDeclId, - pub params: Vec, + pub args: Vec, } impl LuaAttributeUse { - pub fn new(name: LuaTypeDeclId, params: Vec) -> Self { - Self { id: name, params } + pub fn new(name: LuaTypeDeclId, args: Vec) -> Self { + Self { id: name, args } } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs index 56b159115..17b5c1eeb 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs @@ -221,7 +221,7 @@ pub fn check_table_expr( if let Some(attribute_uses) = property.attribute_uses() { for attribute_use in attribute_uses.iter() { if attribute_use.id.get_name() == "skip_diagnostic" { - if let Some(LuaType::DocStringConst(code)) = attribute_use.params.get(0) { + if let Some(LuaType::DocStringConst(code)) = attribute_use.args.get(0) { if code.as_ref() == "table_field" { return Some(false); } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs index 0afacd5d1..372cbaea5 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs @@ -99,7 +99,7 @@ fn check_deprecated( if let Some(attribute_uses) = property.attribute_uses() { for attribute_use in attribute_uses.iter() { if attribute_use.id.get_name() == "deprecated" { - let depreacated_message = match attribute_use.params.first() { + let depreacated_message = match attribute_use.args.first() { Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(), _ => "deprecated".to_string(), }; diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs index 60e0ae58c..944ed38c6 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs @@ -245,8 +245,8 @@ fn infer_type_doc_function( let operator_index = db.get_operator_index(); let operator_ids = operator_index - .get_operators(&type_id.into(), LuaOperatorMetaMethod::Call) - .ok_or(InferFailReason::None)?; + .get_operators(&type_id.clone().into(), LuaOperatorMetaMethod::Call) + .ok_or(InferFailReason::UnResolveOperatorCall)?; let mut overloads = Vec::new(); for overload_id in operator_ids { let operator = operator_index diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_fail_reason.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_fail_reason.rs index 20c5f2193..5e1ee32db 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_fail_reason.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_fail_reason.rs @@ -11,6 +11,7 @@ pub enum InferFailReason { FieldNotFound, UnResolveDeclType(LuaDeclId), UnResolveMemberType(LuaMemberId), + UnResolveOperatorCall, } impl InferFailReason { @@ -22,6 +23,7 @@ impl InferFailReason { | InferFailReason::FieldNotFound | InferFailReason::UnResolveDeclType(_) | InferFailReason::UnResolveMemberType(_) + | InferFailReason::UnResolveOperatorCall ) } } diff --git a/crates/emmylua_code_analysis/src/semantic/mod.rs b/crates/emmylua_code_analysis/src/semantic/mod.rs index 1f64e30ad..ee93bd84f 100644 --- a/crates/emmylua_code_analysis/src/semantic/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/mod.rs @@ -40,7 +40,7 @@ use type_check::is_sub_type_of; pub use visibility::check_export_visibility; use visibility::check_visibility; -use crate::semantic::member::find_members_with_key; +pub use crate::semantic::member::find_members_with_key; use crate::semantic::type_check::check_type_compact_detail; use crate::{Emmyrc, LuaDocument, LuaSemanticDeclId, ModuleInfo, db_index::LuaTypeDeclId}; use crate::{ diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs index e718a1d8d..6094a3a55 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs @@ -361,7 +361,7 @@ pub fn get_index_alias_name( let alias_label = common_property .find_attribute_use(LuaTypeDeclId::new("index_alias"))? - .params + .args .get(0) .map(|param| match param { LuaType::DocStringConst(s) => s.as_ref(), diff --git a/crates/emmylua_parser/src/syntax/node/lua/stat.rs b/crates/emmylua_parser/src/syntax/node/lua/stat.rs index 37ddb2df4..1685dbc1d 100644 --- a/crates/emmylua_parser/src/syntax/node/lua/stat.rs +++ b/crates/emmylua_parser/src/syntax/node/lua/stat.rs @@ -219,6 +219,7 @@ impl LuaLocalStat { self.children() } + /// 仅从`AST`分析, 并不是绝对准确的, 因为允许最后一个表达式返回多个值 pub fn get_local_name_by_value(&self, value: LuaExpr) -> Option { let local_names = self.get_local_name_list(); let value_exprs = self.get_value_exprs().collect::>(); From 482e20e4e21cafc1596d9f02aca10cdf97f853af Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 30 Sep 2025 05:34:42 +0800 Subject: [PATCH 20/30] remove `runtime.class_default_call` --- .../resources/schema.json | 34 ----------- .../src/compilation/analyzer/lua/mod.rs | 1 + .../src/compilation/analyzer/lua/stats.rs | 57 +------------------ .../src/compilation/test/annotation_test.rs | 18 +++--- .../src/compilation/test/overload_test.rs | 21 ++++--- .../src/config/configs/runtime.rs | 18 +----- .../src/db_index/operators/lua_operator.rs | 47 --------------- .../src/handlers/test/inlay_hint_test.rs | 37 +++++++----- 8 files changed, 52 insertions(+), 181 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/schema.json b/crates/emmylua_code_analysis/resources/schema.json index 7296ce833..d9e953a0a 100644 --- a/crates/emmylua_code_analysis/resources/schema.json +++ b/crates/emmylua_code_analysis/resources/schema.json @@ -110,11 +110,6 @@ "runtime": { "$ref": "#/$defs/EmmyrcRuntime", "default": { - "classDefaultCall": { - "forceNonColon": false, - "forceReturnSelf": false, - "functionName": "" - }, "extensions": [], "frameworkVersions": [], "nonstandardSymbol": [], @@ -163,26 +158,6 @@ } }, "$defs": { - "ClassDefaultCall": { - "type": "object", - "properties": { - "forceNonColon": { - "description": "Mandatory non`:` definition. When `function_name` is not empty, it takes effect.", - "type": "boolean", - "default": true - }, - "forceReturnSelf": { - "description": "Force to return `self`.", - "type": "boolean", - "default": true - }, - "functionName": { - "description": "class default overload function. eg. \"__init\".", - "type": "string", - "default": "" - } - } - }, "DiagnosticCode": { "oneOf": [ { @@ -957,15 +932,6 @@ "EmmyrcRuntime": { "type": "object", "properties": { - "classDefaultCall": { - "description": "class default overload function.", - "$ref": "#/$defs/ClassDefaultCall", - "default": { - "forceNonColon": false, - "forceReturnSelf": false, - "functionName": "" - } - }, "extensions": { "description": "file Extensions. eg: .lua, .lua.txt", "type": "array", diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs index f7054f207..9c6fedd3a 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs @@ -110,6 +110,7 @@ impl LuaAnalyzer<'_> { } } + #[allow(unused)] pub fn get_emmyrc(&self) -> &Emmyrc { self.db.get_emmyrc() } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs index 41d15c8eb..2720f9511 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs @@ -1,11 +1,10 @@ use emmylua_parser::{ - BinaryOperator, LuaAssignStat, LuaAstNode, LuaAstToken, LuaExpr, LuaFuncStat, LuaIndexExpr, + BinaryOperator, LuaAssignStat, LuaAstNode, LuaExpr, LuaFuncStat, LuaIndexExpr, LuaLocalFuncStat, LuaLocalStat, LuaTableField, LuaVarExpr, PathTrait, }; use crate::{ - InFiled, InferFailReason, LuaOperator, LuaOperatorMetaMethod, LuaOperatorOwner, LuaTypeCache, - LuaTypeOwner, OperatorFunction, + InFiled, InferFailReason, LuaTypeCache, LuaTypeOwner, compilation::analyzer::{ common::{add_member, bind_type}, unresolve::{UnResolveDecl, UnResolveMember}, @@ -415,8 +414,6 @@ pub fn analyze_func_stat(analyzer: &mut LuaAnalyzer, func_stat: LuaFuncStat) -> .get_type_index_mut() .bind_type(type_owner, LuaTypeCache::InferType(signature_type.clone())); - try_add_class_default_call(analyzer, func_name, signature_type); - Some(()) } @@ -503,53 +500,3 @@ fn special_assign_pattern( Some(()) } - -pub fn try_add_class_default_call( - analyzer: &mut LuaAnalyzer, - func_name: LuaVarExpr, - signature_type: LuaType, -) -> Option<()> { - let LuaType::Signature(signature_id) = signature_type else { - return None; - }; - - let default_name = &analyzer - .get_emmyrc() - .runtime - .class_default_call - .function_name; - - if default_name.is_empty() { - return None; - } - if let LuaVarExpr::IndexExpr(index_expr) = func_name { - let index_key = index_expr.get_index_key()?; - if index_key.get_path_part() == *default_name { - let prefix_expr = index_expr.get_prefix_expr()?; - if let Ok(prefix_type) = analyzer.infer_expr(&prefix_expr) - && let LuaType::Def(decl_id) = prefix_type - { - // 如果已经存在, 则不添加 - let call = analyzer.db.get_operator_index().get_operators( - &LuaOperatorOwner::Type(decl_id.clone()), - LuaOperatorMetaMethod::Call, - ); - if call.is_some() { - return None; - } - - let operator = LuaOperator::new( - decl_id.into(), - LuaOperatorMetaMethod::Call, - analyzer.file_id, - // 必须指向名称, 使用 index_expr 的完整范围不会跳转到函数上 - index_expr.get_name_token()?.syntax().text_range(), - OperatorFunction::DefaultCall(signature_id), - ); - analyzer.db.get_operator_index_mut().add_operator(operator); - } - } - } - - Some(()) -} diff --git a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs index 3dbb971ea..b77111b6a 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs @@ -127,12 +127,16 @@ mod test { #[test] fn test_generic_type_extends() { - let mut ws = VirtualWorkspace::new(); - let mut emmyrc = ws.get_emmyrc(); - emmyrc.runtime.class_default_call.force_non_colon = true; - emmyrc.runtime.class_default_call.force_return_self = true; - emmyrc.runtime.class_default_call.function_name = "__init".to_string(); - ws.update_emmyrc(emmyrc); + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + ws.def( + r#" + ---@generic T + ---@param [class_ctor("__init")] name `T` + ---@return T + function meta(name) + end + "#, + ); ws.def( r#" ---@class State @@ -141,7 +145,7 @@ mod test { ---@class StateMachine ---@field aaa T ---@field new fun(self: self): self - StateMachine = {} + StateMachine = meta("StateMachine") ---@return self function StateMachine:abc() diff --git a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs index e91cc5719..5d2992f22 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs @@ -1,6 +1,5 @@ #[cfg(test)] mod test { - use std::{ops::Deref, sync::Arc}; use crate::{DiagnosticCode, VirtualWorkspace}; @@ -31,18 +30,24 @@ mod test { #[test] fn test_class_default_call() { let mut ws = VirtualWorkspace::new(); - let mut emmyrc = ws.analysis.emmyrc.deref().clone(); - emmyrc.runtime.class_default_call.function_name = "__init".to_string(); - emmyrc.runtime.class_default_call.force_non_colon = true; - emmyrc.runtime.class_default_call.force_return_self = true; - ws.analysis.update_config(Arc::new(emmyrc)); + ws.def( + r#" + ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + + ---@generic T + ---@param [class_ctor("__init")] name `T` + ---@return T + function meta(name) + end + "#, + ); ws.def( r#" ---@class MyClass - local M = {} + local M = meta("MyClass") - function M:__init(a) + function M:__init() end A = M() diff --git a/crates/emmylua_code_analysis/src/config/configs/runtime.rs b/crates/emmylua_code_analysis/src/config/configs/runtime.rs index cd1d7970c..36b7beea1 100644 --- a/crates/emmylua_code_analysis/src/config/configs/runtime.rs +++ b/crates/emmylua_code_analysis/src/config/configs/runtime.rs @@ -24,9 +24,6 @@ pub struct EmmyrcRuntime { #[serde(default)] /// Require pattern. eg. "?.lua", "?/init.lua" pub require_pattern: Vec, - #[serde(default)] - /// class default overload function. - pub class_default_call: ClassDefaultCall, /// Non-standard symbols. #[serde(default)] pub nonstandard_symbol: Vec, @@ -75,20 +72,7 @@ impl EmmyrcLuaVersion { } } -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct ClassDefaultCall { - #[serde(default)] - /// class default overload function. eg. "__init". - pub function_name: String, - #[serde(default = "default_true")] - /// Mandatory non`:` definition. When `function_name` is not empty, it takes effect. - pub force_non_colon: bool, - /// Force to return `self`. - #[serde(default = "default_true")] - pub force_return_self: bool, -} - +#[allow(unused)] fn default_true() -> bool { true } diff --git a/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs b/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs index 4ecbbc72c..ce6d4ede8 100644 --- a/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs +++ b/crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs @@ -23,7 +23,6 @@ pub struct LuaOperator { pub enum OperatorFunction { Func(Arc), Signature(LuaSignatureId), - DefaultCall(LuaSignatureId), DefaultClassCtor { id: LuaSignatureId, strip_self: bool, @@ -78,7 +77,6 @@ impl LuaOperator { LuaType::Any } // 只有 .field 才有`operand`, call 不会有这个 - OperatorFunction::DefaultCall(_) => LuaType::Unknown, OperatorFunction::DefaultClassCtor { .. } => LuaType::Unknown, } } @@ -101,25 +99,6 @@ impl LuaOperator { Ok(LuaType::Any) } - OperatorFunction::DefaultCall(signature_id) => { - let emmyrc = db.get_emmyrc(); - if emmyrc.runtime.class_default_call.force_return_self { - return Ok(LuaType::SelfInfer); - } - - if let Some(signature) = db.get_signature_index().get(signature_id) { - if signature.resolve_return == SignatureReturnStatus::UnResolve { - return Err(InferFailReason::UnResolveSignatureReturn(*signature_id)); - } - - let return_type = signature.return_docs.first(); - if let Some(return_type) = return_type { - return Ok(return_type.type_ref.clone()); - } - } - - Ok(LuaType::Any) - } OperatorFunction::DefaultClassCtor { id, return_self, .. } => { @@ -144,32 +123,6 @@ impl LuaOperator { match &self.func { OperatorFunction::Func(func) => LuaType::DocFunction(func.clone()), OperatorFunction::Signature(signature) => LuaType::Signature(*signature), - OperatorFunction::DefaultCall(signature_id) => { - let emmyrc = db.get_emmyrc(); - - if let Some(signature) = db.get_signature_index().get(signature_id) { - let params = signature.get_type_params(); - let is_colon_define = if emmyrc.runtime.class_default_call.force_non_colon { - false - } else { - signature.is_colon_define - }; - let return_type = if emmyrc.runtime.class_default_call.force_return_self { - LuaType::SelfInfer - } else { - signature.get_return_type() - }; - let func_type = LuaFunctionType::new( - signature.async_state, - is_colon_define, - params, - return_type, - ); - return LuaType::DocFunction(Arc::new(func_type)); - } - - LuaType::Signature(*signature_id) - } OperatorFunction::DefaultClassCtor { id, strip_self, diff --git a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs index b1ea188c8..be75b6d1d 100644 --- a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs +++ b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use googletest::prelude::*; - use std::{ops::Deref, sync::Arc}; use crate::handlers::test_lib::{ProviderVirtualWorkspace, VirtualInlayHint, check}; @@ -138,28 +137,40 @@ mod tests { #[gtest] fn test_class_call_hint() -> Result<()> { let mut ws = ProviderVirtualWorkspace::new(); - let mut emmyrc = ws.analysis.get_emmyrc().deref().clone(); - emmyrc.runtime.class_default_call.function_name = "__init".to_string(); - emmyrc.runtime.class_default_call.force_non_colon = true; - emmyrc.runtime.class_default_call.force_return_self = true; - ws.analysis.update_config(Arc::new(emmyrc)); + ws.def( + r#" + ---@generic T + ---@param [class_ctor("__init")] name `T` + ---@return T + function meta(name) + end + "#, + ); check!(ws.check_inlay_hint( r#" ---@class MyClass - local A + local A = meta("MyClass") function A:__init(a) end A() "#, - vec![VirtualInlayHint { - label: "new".to_string(), - line: 7, - pos: 16, - ref_file: Some("".to_string()), - }] + vec![ + VirtualInlayHint { + label: "name:".to_string(), + line: 2, + pos: 31, + ref_file: Some("".to_string()), + }, + VirtualInlayHint { + label: "new".to_string(), + line: 7, + pos: 16, + ref_file: Some("".to_string()), + } + ] )); Ok(()) } From 5de19d2fc7930c9938814ba5c78451454904ef62 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 30 Sep 2025 20:41:47 +0800 Subject: [PATCH 21/30] fix Clippy --- .vscode/settings.json | 3 +- .../src/compilation/analyzer/lua/call.rs | 41 ++++++++--------- .../compilation/analyzer/unresolve/resolve.rs | 45 +++++++++---------- .../checker/assign_type_mismatch.rs | 2 +- .../src/diagnostic/checker/attribute_check.rs | 4 +- .../add_completions/add_member_completion.rs | 2 +- .../providers/doc_name_token_provider.rs | 6 +-- .../completion/providers/doc_type_provider.rs | 6 +-- .../semantic_token/build_semantic_tokens.rs | 12 +++-- 9 files changed, 56 insertions(+), 65 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c052c5941..ea7222f3d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, - "files.trimTrailingWhitespaceInRegexAndStrings": true + "files.trimTrailingWhitespaceInRegexAndStrings": true, + "rust-analyzer.check.command": "clippy", } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs index 49929692b..83db64e66 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs @@ -7,32 +7,29 @@ use crate::{ pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Option<()> { let prefix_expr = call_expr.clone().get_prefix_expr()?; - match analyzer.infer_expr(&prefix_expr) { - Ok(expr_type) => { - let LuaType::Signature(signature_id) = expr_type else { - return Some(()); - }; - let signature = analyzer.db.get_signature_index().get(&signature_id)?; - for (idx, param_info) in signature.param_docs.iter() { - if let Some(ref attrs) = param_info.attributes { - for attr in attrs.iter() { - if attr.id.get_name() == "class_ctor" { - let unresolve = UnResolveClassCtor { - file_id: analyzer.file_id, - call_expr: call_expr.clone(), - signature_id: signature_id, - param_idx: *idx, - }; - analyzer - .context - .add_unresolve(unresolve.into(), InferFailReason::None); - return Some(()); - } + if let Ok(expr_type) = analyzer.infer_expr(&prefix_expr) { + let LuaType::Signature(signature_id) = expr_type else { + return Some(()); + }; + let signature = analyzer.db.get_signature_index().get(&signature_id)?; + for (idx, param_info) in signature.param_docs.iter() { + if let Some(ref attrs) = param_info.attributes { + for attr in attrs.iter() { + if attr.id.get_name() == "class_ctor" { + let unresolve = UnResolveClassCtor { + file_id: analyzer.file_id, + call_expr: call_expr.clone(), + signature_id, + param_idx: *idx, + }; + analyzer + .context + .add_unresolve(unresolve.into(), InferFailReason::None); + return Some(()); } } } } - Err(_) => {} } Some(()) } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 8222bac74..7d0c6bf6e 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -272,7 +272,7 @@ pub fn try_resolve_class_ctor( .find(|attr| attr.id.get_name() == "class_ctor") .ok_or(InferFailReason::None)?; let LuaType::DocStringConst(target_signature_name) = - class_ctor_use.args.get(0).ok_or(InferFailReason::None)? + class_ctor_use.args.first().ok_or(InferFailReason::None)? else { return Err(InferFailReason::None); }; @@ -365,31 +365,28 @@ fn get_class_ctor_target_type( call_expr: LuaCallExpr, call_index: usize, ) -> Option { - match param_type { - LuaType::StrTplRef(str_tpl) => { - let name = { - let arg_expr = call_expr - .get_args_list()? - .get_args() - .nth(call_index)? - .clone(); - let name = infer_expr(db, cache, arg_expr).ok()?; - match name { - LuaType::StringConst(s) => s.to_string(), - _ => return None, - } - }; - - let prefix = str_tpl.get_prefix(); - let suffix = str_tpl.get_suffix(); - let type_decl_id: LuaTypeDeclId = - LuaTypeDeclId::new(format!("{}{}{}", prefix, name, suffix).as_str()); - let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?; - if type_decl.is_class() { - return Some(type_decl_id); + if let LuaType::StrTplRef(str_tpl) = param_type { + let name = { + let arg_expr = call_expr + .get_args_list()? + .get_args() + .nth(call_index)? + .clone(); + let name = infer_expr(db, cache, arg_expr).ok()?; + match name { + LuaType::StringConst(s) => s.to_string(), + _ => return None, } + }; + + let prefix = str_tpl.get_prefix(); + let suffix = str_tpl.get_suffix(); + let type_decl_id: LuaTypeDeclId = + LuaTypeDeclId::new(format!("{}{}{}", prefix, name, suffix).as_str()); + let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?; + if type_decl.is_class() { + return Some(type_decl_id); } - _ => {} } None diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs index 17b5c1eeb..b020bb23a 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs @@ -221,7 +221,7 @@ pub fn check_table_expr( if let Some(attribute_uses) = property.attribute_uses() { for attribute_use in attribute_uses.iter() { if attribute_use.id.get_name() == "skip_diagnostic" { - if let Some(LuaType::DocStringConst(code)) = attribute_use.args.get(0) { + if let Some(LuaType::DocStringConst(code)) = attribute_use.args.first() { if code.as_ref() == "table_field" { return Some(false); } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs index 0e3d9764a..415bfd255 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs @@ -67,7 +67,7 @@ fn check_param_count( context: &mut DiagnosticContext, def_params: &[(String, Option)], attribute_use: &LuaDocAttributeUse, - args: &Vec, + args: &[LuaLiteralExpr], ) -> Option<()> { let call_args_count = args.len(); // 调用参数少于定义参数, 需要考虑可空参数 @@ -76,7 +76,7 @@ fn check_param_count( if def_param.0 == "..." { break; } - if def_param.1.as_ref().is_some_and(|typ| is_nullable(typ)) { + if def_param.1.as_ref().is_some_and(is_nullable) { continue; } context.add_diagnostic( diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs index 6094a3a55..6f5ea4b96 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs @@ -362,7 +362,7 @@ pub fn get_index_alias_name( let alias_label = common_property .find_attribute_use(LuaTypeDeclId::new("index_alias"))? .args - .get(0) + .first() .map(|param| match param { LuaType::DocStringConst(s) => s.as_ref(), _ => "", diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs index 87c5f92f1..f39b5744c 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs @@ -85,7 +85,7 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option { - let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + let attr = LuaDocTypeFlag::cast(parent.clone())?; Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, @@ -106,7 +106,7 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option Some(DocCompletionExpected::DiagnosticCode), LuaSyntaxKind::DocTypeFlag => { - let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + let attr = LuaDocTypeFlag::cast(parent.clone())?; Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, @@ -116,7 +116,7 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option { - let attr = LuaDocTypeFlag::cast(parent.clone().into())?; + let attr = LuaDocTypeFlag::cast(parent.clone())?; Some(DocCompletionExpected::ClassFlag(attr)) } _ => None, diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs index 00ae92d40..c8aad985f 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs @@ -87,7 +87,7 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option { let parent = builder.trigger_token.parent()?; if let Some(doc_name) = LuaDocNameType::cast(parent) { - if let Some(_) = doc_name.get_parent::() { + if doc_name.get_parent::().is_some() { return Some(CompletionType::AttributeUse); } return Some(CompletionType::Type); @@ -128,9 +128,7 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option { - return Some(CompletionType::AttributeUse); - } + LuaTokenKind::TkDocAttributeUse => Some(CompletionType::AttributeUse), _ => None, } } diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index fbb44e07a..da85f72c6 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -785,14 +785,12 @@ fn build_node_semantic_token( } LuaAst::LuaDocTagAttributeUse(tag_use) => { // 给 `@[]` 染色, @已经染色过了 - tag_use - .token_by_kind(LuaTokenKind::TkDocAttributeUse) - .map(|token| { - builder.push(token.syntax(), SemanticTokenType::KEYWORD); - }); - tag_use.syntax().last_token().map(|token| { + 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); - }); + } } _ => {} } From ba9d4d3d5d209d750218b06671069b6c16ff980d Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 30 Sep 2025 21:21:22 +0800 Subject: [PATCH 22/30] Prevent Potential Subtraction Overflow --- crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs index 0b607aeae..3133f3b81 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs @@ -251,7 +251,7 @@ fn infer_table_type_by_calleee( call_arg_number += 1; } (true, false) => { - call_arg_number -= 1; + call_arg_number = call_arg_number.saturating_sub(1); } } let typ = param_types From 8b6167b65451596b185fd8870361c88a11600f16 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 9 Oct 2025 15:55:06 +0800 Subject: [PATCH 23/30] fix typos --- .../src/diagnostic/checker/deprecated.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs index 372cbaea5..9ebc7a822 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs @@ -99,16 +99,11 @@ fn check_deprecated( if let Some(attribute_uses) = property.attribute_uses() { for attribute_use in attribute_uses.iter() { if attribute_use.id.get_name() == "deprecated" { - let depreacated_message = match attribute_use.args.first() { + let deprecated_message = match attribute_use.args.first() { Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(), _ => "deprecated".to_string(), }; - context.add_diagnostic( - DiagnosticCode::Deprecated, - range, - depreacated_message, - None, - ); + context.add_diagnostic(DiagnosticCode::Deprecated, range, deprecated_message, None); } } } From 05eb74124d8e4a442d021c64ecb9aa5c5cf85392 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 13 Oct 2025 12:19:55 +0800 Subject: [PATCH 24/30] =?UTF-8?q?=E5=B0=86=E7=89=B9=E6=80=A7`class=5Fctor`?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E8=B0=83=E6=95=B4=E4=B8=BA`constructor`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/std/builtin.lua | 2 +- .../src/compilation/analyzer/lua/call.rs | 6 ++-- .../src/compilation/analyzer/unresolve/mod.rs | 18 ++++++------ .../compilation/analyzer/unresolve/resolve.rs | 29 +++++++++---------- .../src/compilation/test/annotation_test.rs | 2 +- .../src/compilation/test/attribute_test.rs | 10 +++---- .../src/compilation/test/overload_test.rs | 4 +-- .../src/handlers/test/inlay_hint_test.rs | 2 +- 8 files changed, 36 insertions(+), 37 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index 6c89b8f73..1adb5e36b 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -162,4 +162,4 @@ --- - `name` - The name of the constructor --- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true` --- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true` ----@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) +---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs index 83db64e66..f433d5749 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs @@ -2,7 +2,7 @@ use emmylua_parser::LuaCallExpr; use crate::{ InferFailReason, LuaType, - compilation::analyzer::{lua::LuaAnalyzer, unresolve::UnResolveClassCtor}, + compilation::analyzer::{lua::LuaAnalyzer, unresolve::UnResolveConstructor}, }; pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Option<()> { @@ -15,8 +15,8 @@ pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Optio for (idx, param_info) in signature.param_docs.iter() { if let Some(ref attrs) = param_info.attributes { for attr in attrs.iter() { - if attr.id.get_name() == "class_ctor" { - let unresolve = UnResolveClassCtor { + if attr.id.get_name() == "constructor" { + let unresolve = UnResolveConstructor { file_id: analyzer.file_id, call_expr: call_expr.clone(), signature_id, diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs index eafb971ab..f6621fac9 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use crate::{ FileId, InferFailReason, LuaMemberFeature, LuaSemanticDeclId, - compilation::analyzer::{AnalysisPipeline, unresolve::resolve::try_resolve_class_ctor}, + compilation::analyzer::{AnalysisPipeline, unresolve::resolve::try_resolve_constructor}, db_index::{DbIndex, LuaDeclId, LuaMemberId, LuaSignatureId}, profile::Profile, }; @@ -201,8 +201,8 @@ fn try_resolve( UnResolve::TableField(un_resolve_table_field) => { try_resolve_table_field(db, cache, un_resolve_table_field) } - UnResolve::ClassCtor(un_resolve_class_ctor) => { - try_resolve_class_ctor(db, cache, un_resolve_class_ctor) + UnResolve::ClassCtor(un_resolve_constructor) => { + try_resolve_constructor(db, cache, un_resolve_constructor) } }; @@ -263,7 +263,7 @@ pub enum UnResolve { ClosureParentParams(Box), ModuleRef(Box), TableField(Box), - ClassCtor(Box), + ClassCtor(Box), } #[allow(dead_code)] @@ -286,7 +286,7 @@ impl UnResolve { } UnResolve::TableField(un_resolve_table_field) => Some(un_resolve_table_field.file_id), UnResolve::ModuleRef(_) => None, - UnResolve::ClassCtor(un_resolve_class_ctor) => Some(un_resolve_class_ctor.file_id), + UnResolve::ClassCtor(un_resolve_constructor) => Some(un_resolve_constructor.file_id), } } } @@ -435,15 +435,15 @@ impl From for UnResolve { } #[derive(Debug)] -pub struct UnResolveClassCtor { +pub struct UnResolveConstructor { pub file_id: FileId, pub call_expr: LuaCallExpr, pub signature_id: LuaSignatureId, pub param_idx: usize, } -impl From for UnResolve { - fn from(un_resolve_class_ctor: UnResolveClassCtor) -> Self { - UnResolve::ClassCtor(Box::new(un_resolve_class_ctor)) +impl From for UnResolve { + fn from(un_resolve_constructor: UnResolveConstructor) -> Self { + UnResolve::ClassCtor(Box::new(un_resolve_constructor)) } } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 7d0c6bf6e..73230e8d6 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -11,7 +11,7 @@ use crate::{ compilation::analyzer::{ common::{add_member, bind_type}, lua::{analyze_return_point, infer_for_range_iter_expr_func}, - unresolve::UnResolveClassCtor, + unresolve::UnResolveConstructor, }, db_index::{DbIndex, LuaMemberOwner, LuaType}, find_members_with_key, @@ -252,36 +252,35 @@ pub fn try_resolve_module_ref( Ok(()) } -#[allow(unused)] -pub fn try_resolve_class_ctor( +pub fn try_resolve_constructor( db: &mut DbIndex, cache: &mut LuaInferCache, - unresolve_class_ctor: &mut UnResolveClassCtor, + unresolve_constructor: &mut UnResolveConstructor, ) -> ResolveResult { let signature = db .get_signature_index() - .get(&unresolve_class_ctor.signature_id) + .get(&unresolve_constructor.signature_id) .ok_or(InferFailReason::None)?; let param_info = signature - .get_param_info_by_id(unresolve_class_ctor.param_idx) + .get_param_info_by_id(unresolve_constructor.param_idx) .ok_or(InferFailReason::None)?; - let class_ctor_use = param_info + let constructor_use = param_info .attributes .iter() .flatten() - .find(|attr| attr.id.get_name() == "class_ctor") + .find(|attr| attr.id.get_name() == "constructor") .ok_or(InferFailReason::None)?; let LuaType::DocStringConst(target_signature_name) = - class_ctor_use.args.first().ok_or(InferFailReason::None)? + constructor_use.args.first().ok_or(InferFailReason::None)? else { return Err(InferFailReason::None); }; - let target_type_decl_id = get_class_ctor_target_type( + let target_type_decl_id = get_constructor_target_type( db, cache, ¶m_info.type_ref, - unresolve_class_ctor.call_expr.clone(), - unresolve_class_ctor.param_idx, + unresolve_constructor.call_expr.clone(), + unresolve_constructor.param_idx, ) .ok_or(InferFailReason::None)?; let target_type = LuaType::Ref(target_type_decl_id); @@ -290,14 +289,14 @@ pub fn try_resolve_class_ctor( find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?; let ctor_signature_member = members.first().ok_or(InferFailReason::None)?; let strip_self = { - if let Some(LuaType::DocBooleanConst(strip_self)) = class_ctor_use.args.get(1) { + if let Some(LuaType::DocBooleanConst(strip_self)) = constructor_use.args.get(1) { *strip_self } else { true } }; let return_self = { - if let Some(LuaType::DocBooleanConst(return_self)) = class_ctor_use.args.get(2) { + if let Some(LuaType::DocBooleanConst(return_self)) = constructor_use.args.get(2) { *return_self } else { true @@ -358,7 +357,7 @@ fn set_signature_to_default_call( Some(()) } -fn get_class_ctor_target_type( +fn get_constructor_target_type( db: &DbIndex, cache: &mut LuaInferCache, param_type: &LuaType, diff --git a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs index b77111b6a..d374f770b 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs @@ -131,7 +131,7 @@ mod test { ws.def( r#" ---@generic T - ---@param [class_ctor("__init")] name `T` + ---@param [constructor("__init")] name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index 5d35d5446..1030353dc 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -66,7 +66,7 @@ mod test { } #[test] - fn test_class_ctor() { + fn test_constructor() { let mut ws = VirtualWorkspace::new(); ws.def_files(vec![ @@ -79,10 +79,10 @@ mod test { ( "meta.lua", r#" - ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) ---@generic T - ---@param [class_ctor("__init")] name `T` + ---@param [constructor("__init")] name `T` ---@return T function meta(name) end @@ -92,10 +92,10 @@ mod test { // ws.def( // r#" - // ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + // ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) // ---@generic T - // ---@param [class_ctor("__init")] name `T` + // ---@param [constructor("__init")] name `T` // ---@return T // function meta(name) // end diff --git a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs index 5d2992f22..1c984d87e 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs @@ -32,10 +32,10 @@ mod test { let mut ws = VirtualWorkspace::new(); ws.def( r#" - ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?) + ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) ---@generic T - ---@param [class_ctor("__init")] name `T` + ---@param [constructor("__init")] name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs index be75b6d1d..90c09534e 100644 --- a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs +++ b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs @@ -140,7 +140,7 @@ mod tests { ws.def( r#" ---@generic T - ---@param [class_ctor("__init")] name `T` + ---@param [constructor("__init")] name `T` ---@return T function meta(name) end From 07b7bd0e2831489256f9b6f9140afec597279d17 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 14 Oct 2025 12:56:45 +0800 Subject: [PATCH 25/30] rename `LuaTypeAttribute` to `LuaTypeFlag` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 彻底区分语义 --- .gitignore | 4 ++ .../src/compilation/analyzer/decl/docs.rs | 44 ++++++++--------- .../doc/field_or_operator_def_tags.rs | 2 +- .../src/db_index/type/mod.rs | 4 +- .../src/db_index/type/test.rs | 14 +++--- .../src/db_index/type/type_decl.rs | 14 +++--- .../src/diagnostic/checker/duplicate_type.rs | 20 ++++---- .../providers/doc_name_token_provider.rs | 49 +++++++++---------- .../semantic_token/build_semantic_tokens.rs | 4 +- .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 10 ++-- .../emmylua_parser/src/syntax/node/doc/tag.rs | 6 +-- 11 files changed, 84 insertions(+), 87 deletions(-) diff --git a/.gitignore b/.gitignore index 1c14bf8f2..b2e5b461e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ .idea .cursor dhat-heap.json +.claude + +# MCP, 用于为AI提供lsp上下文 +.serena diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs index 5f18e9b65..7951b6490 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/docs.rs @@ -8,7 +8,7 @@ use rowan::TextRange; use crate::{ LuaTypeDecl, LuaTypeDeclId, - db_index::{LuaDeclTypeKind, LuaTypeAttribute}, + db_index::{LuaDeclTypeKind, LuaTypeFlag}, }; use super::DeclAnalyzer; @@ -17,39 +17,39 @@ pub fn analyze_doc_tag_class(analyzer: &mut DeclAnalyzer, class: LuaDocTagClass) let name_token = class.get_name_token()?; let name = name_token.get_name_text().to_string(); let range = name_token.syntax().text_range(); - let attrib = get_attrib_value(analyzer, class.get_attrib()); + let type_flag = get_type_flag_value(analyzer, class.get_type_flag()); - add_type_decl(analyzer, &name, range, LuaDeclTypeKind::Class, attrib); + add_type_decl(analyzer, &name, range, LuaDeclTypeKind::Class, type_flag); Some(()) } -fn get_attrib_value( +fn get_type_flag_value( analyzer: &mut DeclAnalyzer, - attrib: Option, -) -> FlagSet { - let mut attr: FlagSet = if analyzer.is_meta { - LuaTypeAttribute::Meta.into() + flag: Option, +) -> FlagSet { + let mut attr: FlagSet = if analyzer.is_meta { + LuaTypeFlag::Meta.into() } else { - LuaTypeAttribute::None.into() + LuaTypeFlag::None.into() }; - if let Some(attrib) = attrib { - for token in attrib.get_attrib_tokens() { + if let Some(flag) = flag { + for token in flag.get_attrib_tokens() { match token.get_name_text() { "partial" => { - attr |= LuaTypeAttribute::Partial; + attr |= LuaTypeFlag::Partial; } "key" => { - attr |= LuaTypeAttribute::Key; + attr |= LuaTypeFlag::Key; } // "global" => { // attr |= LuaTypeAttribute::Global; // } "exact" => { - attr |= LuaTypeAttribute::Exact; + attr |= LuaTypeFlag::Exact; } "constructor" => { - attr |= LuaTypeAttribute::Constructor; + attr |= LuaTypeFlag::Constructor; } _ => {} } @@ -63,9 +63,9 @@ pub fn analyze_doc_tag_enum(analyzer: &mut DeclAnalyzer, enum_: LuaDocTagEnum) - let name_token = enum_.get_name_token()?; let name = name_token.get_name_text().to_string(); let range = name_token.syntax().text_range(); - let attrib = get_attrib_value(analyzer, enum_.get_attrib()); + let flag = get_type_flag_value(analyzer, enum_.get_type_flag()); - add_type_decl(analyzer, &name, range, LuaDeclTypeKind::Enum, attrib); + add_type_decl(analyzer, &name, range, LuaDeclTypeKind::Enum, flag); Some(()) } @@ -79,7 +79,7 @@ pub fn analyze_doc_tag_alias(analyzer: &mut DeclAnalyzer, alias: LuaDocTagAlias) &name, range, LuaDeclTypeKind::Alias, - LuaTypeAttribute::None.into(), + LuaTypeFlag::None.into(), ); Some(()) } @@ -92,14 +92,12 @@ pub fn analyze_doc_tag_attribute( let name = name_token.get_name_text().to_string(); let range = name_token.syntax().text_range(); - // LuaTypeAttribute 与 LuaDocTagAttribute 完全是两个不同的概念. - // LuaTypeAttribute 描述类型的属性, LuaDocTagAttribute 类似于 C# 的特性, 附加了更多的信息. add_type_decl( analyzer, &name, range, LuaDeclTypeKind::Attribute, - LuaTypeAttribute::None.into(), + LuaTypeFlag::None.into(), ); Some(()) } @@ -187,7 +185,7 @@ fn add_type_decl( name: &str, range: TextRange, kind: LuaDeclTypeKind, - attrib: FlagSet, + flag: FlagSet, ) { let file_id = analyzer.get_file_id(); let type_index = analyzer.db.get_type_index_mut(); @@ -201,6 +199,6 @@ fn add_type_decl( let simple_name = id.get_simple_name(); type_index.add_type_decl( file_id, - LuaTypeDecl::new(file_id, range, simple_name.to_string(), kind, attrib, id), + LuaTypeDecl::new(file_id, range, simple_name.to_string(), kind, flag, id), ); } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/field_or_operator_def_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/field_or_operator_def_tags.rs index cda01472b..8ddc86fb7 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/field_or_operator_def_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/field_or_operator_def_tags.rs @@ -199,7 +199,7 @@ pub fn analyze_operator(analyzer: &mut DocAnalyzer, tag: LuaDocTagOperator) -> O } fn get_visibility_from_field_attrib(tag: &LuaDocTagField) -> Option { - if let Some(attrib) = tag.get_attrib() { + if let Some(attrib) = tag.get_type_flag() { for token in attrib.get_attrib_tokens() { let visibility = VisibilityKind::to_visibility_kind(token.get_name_text()); if visibility.is_some() { diff --git a/crates/emmylua_code_analysis/src/db_index/type/mod.rs b/crates/emmylua_code_analysis/src/db_index/type/mod.rs index dd1694dda..4876db503 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/mod.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/mod.rs @@ -12,9 +12,7 @@ use crate::{DbIndex, FileId, InFiled}; pub use generic_param::GenericParam; pub use humanize_type::{RenderLevel, format_union_type, humanize_type}; use std::collections::{HashMap, HashSet}; -pub use type_decl::{ - LuaDeclLocation, LuaDeclTypeKind, LuaTypeAttribute, LuaTypeDecl, LuaTypeDeclId, -}; +pub use type_decl::{LuaDeclLocation, LuaDeclTypeKind, LuaTypeDecl, LuaTypeDeclId, LuaTypeFlag}; pub use type_ops::TypeOps; pub use type_owner::{LuaTypeCache, LuaTypeOwner}; pub use type_visit_trait::TypeVisitTrait; diff --git a/crates/emmylua_code_analysis/src/db_index/type/test.rs b/crates/emmylua_code_analysis/src/db_index/type/test.rs index 40667fa4c..e9df26636 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/test.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/test.rs @@ -4,7 +4,7 @@ mod test { use crate::db_index::traits::LuaIndex; use crate::db_index::r#type::LuaTypeIndex; - use crate::db_index::{LuaDeclTypeKind, LuaTypeAttribute}; + use crate::db_index::{LuaDeclTypeKind, LuaTypeFlag}; use crate::{FileId, LuaTypeDecl, LuaTypeDeclId}; fn create_type_index() -> LuaTypeIndex { @@ -26,7 +26,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Alias, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new("test.new_type"), ), ); @@ -62,7 +62,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Alias, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new("test.new_type"), ), ); @@ -92,7 +92,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Class, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new("new_type"), ), ); @@ -110,7 +110,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Class, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new(".new_type"), ), ); @@ -123,7 +123,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Class, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new("new_type"), ), ); @@ -150,7 +150,7 @@ mod test { TextRange::new(0.into(), 4.into()), "new_type".to_string(), LuaDeclTypeKind::Class, - LuaTypeAttribute::Partial.into(), + LuaTypeFlag::Partial.into(), LuaTypeDeclId::new("test.new_type"), ), ); diff --git a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs index 7a7619372..1140348e9 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/type_decl.rs @@ -19,7 +19,7 @@ pub enum LuaDeclTypeKind { } flags! { - pub enum LuaTypeAttribute: u8 { + pub enum LuaTypeFlag: u8 { None, Key, Partial, @@ -43,7 +43,7 @@ impl LuaTypeDecl { range: TextRange, name: String, kind: LuaDeclTypeKind, - attrib: FlagSet, + flag: FlagSet, id: LuaTypeDeclId, ) -> Self { Self { @@ -51,7 +51,7 @@ impl LuaTypeDecl { locations: vec![LuaDeclLocation { file_id, range, - attrib, + flag, }], id, extra: match kind { @@ -94,19 +94,19 @@ impl LuaTypeDecl { pub fn is_exact(&self) -> bool { self.locations .iter() - .any(|l| l.attrib.contains(LuaTypeAttribute::Exact)) + .any(|l| l.flag.contains(LuaTypeFlag::Exact)) } pub fn is_partial(&self) -> bool { self.locations .iter() - .any(|l| l.attrib.contains(LuaTypeAttribute::Partial)) + .any(|l| l.flag.contains(LuaTypeFlag::Partial)) } pub fn is_enum_key(&self) -> bool { self.locations .iter() - .any(|l| l.attrib.contains(LuaTypeAttribute::Key)) + .any(|l| l.flag.contains(LuaTypeFlag::Key)) } pub fn get_id(&self) -> LuaTypeDeclId { @@ -321,7 +321,7 @@ impl<'de> Deserialize<'de> for LuaTypeDeclId { pub struct LuaDeclLocation { pub file_id: FileId, pub range: TextRange, - pub attrib: FlagSet, + pub flag: FlagSet, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_type.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_type.rs index 2cfc4f446..640c8bf62 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_type.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_type.rs @@ -2,7 +2,7 @@ use emmylua_parser::{ LuaAstNode, LuaAstToken, LuaDocTag, LuaDocTagAlias, LuaDocTagClass, LuaDocTagEnum, }; -use crate::{DiagnosticCode, LuaTypeAttribute, SemanticModel}; +use crate::{DiagnosticCode, LuaTypeFlag, SemanticModel}; use super::{Checker, DiagnosticContext}; @@ -45,13 +45,13 @@ fn check_duplicate_class(context: &mut DiagnosticContext, class_tag: LuaDocTagCl let mut partial_times = 0; let mut constructor_times = 0; for location in locations { - let attrib = location.attrib; - if attrib.contains(LuaTypeAttribute::Meta) { + let flag = location.flag; + if flag.contains(LuaTypeFlag::Meta) { continue; } - if attrib.contains(LuaTypeAttribute::Partial) { + if flag.contains(LuaTypeFlag::Partial) { partial_times += 1; - } else if attrib.contains(LuaTypeAttribute::Constructor) { + } else if flag.contains(LuaTypeFlag::Constructor) { constructor_times += 1; } else { type_times += 1; @@ -104,11 +104,11 @@ fn check_duplicate_enum(context: &mut DiagnosticContext, enum_tag: LuaDocTagEnum let mut type_times = 0; let mut partial_times = 0; for location in locations { - let attrib = location.attrib; - if attrib.contains(LuaTypeAttribute::Meta) { + let flag = location.flag; + if flag.contains(LuaTypeFlag::Meta) { continue; } - if attrib.contains(LuaTypeAttribute::Partial) { + if flag.contains(LuaTypeFlag::Partial) { partial_times += 1; } else { type_times += 1; @@ -148,8 +148,8 @@ fn check_duplicate_alias(context: &mut DiagnosticContext, alias_tag: LuaDocTagAl if locations.len() > 1 { let mut type_times = 0; for location in locations { - let attrib = location.attrib; - if !attrib.contains(LuaTypeAttribute::Meta) { + let flag = location.flag; + if !flag.contains(LuaTypeFlag::Meta) { type_times += 1; } } diff --git a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs index f39b5744c..982745c42 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/doc_name_token_provider.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use emmylua_code_analysis::{DiagnosticCode, LuaTypeAttribute}; +use emmylua_code_analysis::{DiagnosticCode, LuaTypeFlag}; use emmylua_parser::{ LuaAst, LuaAstNode, LuaClosureExpr, LuaComment, LuaDocTag, LuaDocTypeFlag, LuaSyntaxKind, LuaSyntaxToken, LuaTokenKind, @@ -29,8 +29,8 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { DocCompletionExpected::DiagnosticCode => { add_tag_diagnostic_code_completion(builder); } - DocCompletionExpected::ClassFlag(node) => { - add_tag_class_attr_completion(builder, node); + DocCompletionExpected::TypeFlag(node) => { + add_tag_type_flag_completion(builder, node); } DocCompletionExpected::Namespace => { add_tag_namespace_completion(builder); @@ -84,10 +84,9 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option { Some(DocCompletionExpected::DiagnosticCode) } - LuaSyntaxKind::DocTypeFlag => { - let attr = LuaDocTypeFlag::cast(parent.clone())?; - Some(DocCompletionExpected::ClassFlag(attr)) - } + LuaSyntaxKind::DocTypeFlag => Some(DocCompletionExpected::TypeFlag( + LuaDocTypeFlag::cast(parent.clone())?, + )), _ => None, } } @@ -105,20 +104,18 @@ fn get_doc_completion_expected(trigger_token: &LuaSyntaxToken) -> Option Some(DocCompletionExpected::DiagnosticCode), - LuaSyntaxKind::DocTypeFlag => { - let attr = LuaDocTypeFlag::cast(parent.clone())?; - Some(DocCompletionExpected::ClassFlag(attr)) - } + LuaSyntaxKind::DocTypeFlag => Some(DocCompletionExpected::TypeFlag( + LuaDocTypeFlag::cast(parent.clone())?, + )), _ => None, } } LuaTokenKind::TkLeftParen => { let parent = trigger_token.parent()?; match parent.kind().into() { - LuaSyntaxKind::DocTypeFlag => { - let attr = LuaDocTypeFlag::cast(parent.clone())?; - Some(DocCompletionExpected::ClassFlag(attr)) - } + LuaSyntaxKind::DocTypeFlag => Some(DocCompletionExpected::TypeFlag( + LuaDocTypeFlag::cast(parent.clone())?, + )), _ => None, } } @@ -132,7 +129,7 @@ enum DocCompletionExpected { Cast, DiagnosticAction, DiagnosticCode, - ClassFlag(LuaDocTypeFlag), + TypeFlag(LuaDocTypeFlag), Namespace, Using, Export, @@ -228,33 +225,33 @@ fn add_tag_diagnostic_code_completion(builder: &mut CompletionBuilder) { } } -fn add_tag_class_attr_completion( +fn add_tag_type_flag_completion( builder: &mut CompletionBuilder, node: LuaDocTypeFlag, ) -> Option<()> { - let mut attributes = vec![(LuaTypeAttribute::Partial, "partial")]; + let mut flags = vec![(LuaTypeFlag::Partial, "partial")]; match LuaDocTag::cast(node.syntax().parent()?)? { LuaDocTag::Alias(_) => {} LuaDocTag::Class(_) => { - attributes.push((LuaTypeAttribute::Exact, "exact")); - attributes.push((LuaTypeAttribute::Constructor, "constructor")); + flags.push((LuaTypeFlag::Exact, "exact")); + flags.push((LuaTypeFlag::Constructor, "constructor")); } LuaDocTag::Enum(_) => { - attributes.insert(0, (LuaTypeAttribute::Key, "key")); - attributes.push((LuaTypeAttribute::Exact, "exact")); + flags.insert(0, (LuaTypeFlag::Key, "key")); + flags.push((LuaTypeFlag::Exact, "exact")); } _ => {} } // 已存在的属性 - let mut existing_attrs = HashSet::new(); + let mut existing_flags = HashSet::new(); for token in node.get_attrib_tokens() { let name_text = token.get_name_text().to_string(); - existing_attrs.insert(name_text); + existing_flags.insert(name_text); } - for (_, name) in attributes.iter() { - if existing_attrs.contains(*name) { + for (_, name) in flags.iter() { + if existing_flags.contains(*name) { continue; } let completion_item = CompletionItem { diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index da85f72c6..f1b31e37f 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -306,7 +306,7 @@ fn build_node_semantic_token( SemanticTokenModifier::DECLARATION, ); } - if let Some(attribs) = doc_class.get_attrib() { + if let Some(attribs) = doc_class.get_type_flag() { for token in attribs.tokens::() { builder.push(token.syntax(), SemanticTokenType::DECORATOR); } @@ -330,7 +330,7 @@ fn build_node_semantic_token( SemanticTokenType::ENUM, SemanticTokenModifier::DECLARATION, ); - if let Some(attribs) = doc_enum.get_attrib() { + if let Some(attribs) = doc_enum.get_type_flag() { for token in attribs.tokens::() { builder.push(token.syntax(), SemanticTokenType::DECORATOR); } diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index 2b05efa55..3b95b3947 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -144,17 +144,17 @@ impl LuaDocLexer<'_> { reader.eat_while(is_doc_whitespace); LuaTokenKind::TkWhitespace } - '[' => { - reader.bump(); - self.state = LuaDocLexerState::AttributeUse; - LuaTokenKind::TkDocAttributeUse - } ch if is_name_start(ch) => { reader.bump(); reader.eat_while(is_name_continue); let text = reader.current_text(); to_tag(text) } + '[' => { + reader.bump(); + self.state = LuaDocLexerState::AttributeUse; + LuaTokenKind::TkDocAttributeUse + } _ => { reader.eat_while(|_| true); LuaTokenKind::TkDocTrivia diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index ecb121ddf..14a8608db 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -264,7 +264,7 @@ impl LuaDocTagClass { self.child() } - pub fn get_attrib(&self) -> Option { + pub fn get_type_flag(&self) -> Option { self.child() } } @@ -313,7 +313,7 @@ impl LuaDocTagEnum { self.child() } - pub fn get_attrib(&self) -> Option { + pub fn get_type_flag(&self) -> Option { self.child() } } @@ -706,7 +706,7 @@ impl LuaDocTagField { self.token() } - pub fn get_attrib(&self) -> Option { + pub fn get_type_flag(&self) -> Option { self.child() } } From 7eb8cf4326d8bc36b0e15e07b16560e181741f37 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 14 Oct 2025 17:10:44 +0800 Subject: [PATCH 26/30] update attribute --- .../providers/auto_require_provider.rs | 4 +- .../semantic_token/build_semantic_tokens.rs | 39 +++++++++++++++++-- crates/emmylua_ls/src/util/mod.rs | 2 +- .../src/util/module_name_convert.rs | 14 +++---- .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 4 -- 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs index 32bd29d7f..e6a3b73cc 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs @@ -12,7 +12,7 @@ use crate::{ completion_data::CompletionData, }, }, - util::{key_name_convert, module_name_convert}, + util::{flie_name_convert, module_name_convert}, }; pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { @@ -136,7 +136,7 @@ fn try_add_member_completion_items( LuaType::TableConst(_) | LuaType::Def(_) => { let member_infos = builder.semantic_model.get_member_infos(export_type)?; for member_info in member_infos { - let key_name = key_name_convert( + let key_name = flie_name_convert( &member_info.key.to_path(), &member_info.typ, file_conversion, diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index f1b31e37f..6c2e0fe1f 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -12,8 +12,8 @@ use emmylua_code_analysis::{ }; use emmylua_parser::{ LuaAst, LuaAstNode, LuaAstToken, LuaCallArgList, LuaCallExpr, LuaComment, LuaDocFieldKey, - LuaDocObjectFieldKey, LuaExpr, LuaGeneralToken, LuaKind, LuaLiteralToken, LuaNameToken, - LuaSyntaxKind, LuaSyntaxNode, LuaSyntaxToken, LuaTokenKind, LuaVarExpr, + LuaDocObjectFieldKey, LuaDocType, LuaExpr, LuaGeneralToken, LuaKind, LuaLiteralToken, + LuaNameToken, LuaSyntaxKind, LuaSyntaxNode, LuaSyntaxToken, LuaTokenKind, LuaVarExpr, }; use emmylua_parser_desc::{CodeBlockHighlightKind, DescItem, DescItemKind}; use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType}; @@ -202,7 +202,8 @@ fn build_tokens_semantic_token( | LuaTokenKind::TkTagSource | LuaTokenKind::TkTagReturnCast | LuaTokenKind::TkTagExport - | LuaTokenKind::TkLanguage => { + | LuaTokenKind::TkLanguage + | LuaTokenKind::TkTagAttribute => { builder.push_with_modifier( token, SemanticTokenType::KEYWORD, @@ -784,13 +785,43 @@ fn build_node_semantic_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); + } + } + } } _ => {} } diff --git a/crates/emmylua_ls/src/util/mod.rs b/crates/emmylua_ls/src/util/mod.rs index 3c1a29931..af6f80a71 100644 --- a/crates/emmylua_ls/src/util/mod.rs +++ b/crates/emmylua_ls/src/util/mod.rs @@ -3,5 +3,5 @@ mod module_name_convert; mod time_cancel_token; pub use desc::*; -pub use module_name_convert::{key_name_convert, module_name_convert}; +pub use module_name_convert::{flie_name_convert, module_name_convert}; pub use time_cancel_token::time_cancel_token; diff --git a/crates/emmylua_ls/src/util/module_name_convert.rs b/crates/emmylua_ls/src/util/module_name_convert.rs index ceb066713..3ff29a1be 100644 --- a/crates/emmylua_ls/src/util/module_name_convert.rs +++ b/crates/emmylua_ls/src/util/module_name_convert.rs @@ -29,30 +29,30 @@ pub fn module_name_convert( module_name } -pub fn key_name_convert( +pub fn flie_name_convert( key: &str, typ: &LuaType, file_conversion: EmmyrcFilenameConvention, ) -> String { - let mut key_name = key.to_string(); + let mut flie_name = key.to_string(); match file_conversion { EmmyrcFilenameConvention::SnakeCase => { - key_name = to_snake_case(&key_name); + flie_name = to_snake_case(&flie_name); } EmmyrcFilenameConvention::CamelCase => { - key_name = to_camel_case(&key_name); + flie_name = to_camel_case(&flie_name); } EmmyrcFilenameConvention::PascalCase => { - key_name = to_pascal_case(&key_name); + flie_name = to_pascal_case(&flie_name); } EmmyrcFilenameConvention::Keep => {} EmmyrcFilenameConvention::KeepClass => { if let LuaType::Def(id) = typ { - key_name = id.get_simple_name().to_string(); + flie_name = id.get_simple_name().to_string(); } } } - key_name + flie_name } fn to_snake_case(s: &str) -> String { diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index 3b95b3947..9d770299e 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -619,10 +619,6 @@ impl LuaDocLexer<'_> { reader.bump(); LuaTokenKind::TkComma } - '=' => { - reader.bump(); - LuaTokenKind::TkAssign - } ']' => { reader.bump(); LuaTokenKind::TkRightBracket From e642ebfd1129c51d03280de2bc872490e6682096 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 15 Oct 2025 12:35:25 +0800 Subject: [PATCH 27/30] attribute: add field_accessor --- Cargo.lock | 1 + .../resources/std/builtin.lua | 11 +- .../analyzer/doc/attribute_tags.rs | 46 +++++- .../compilation/analyzer/unresolve/resolve.rs | 41 +++-- .../src/compilation/test/attribute_test.rs | 94 ++--------- .../src/db_index/property/property.rs | 13 +- .../checker/assign_type_mismatch.rs | 23 +-- .../src/diagnostic/checker/deprecated.rs | 9 +- crates/emmylua_ls/Cargo.toml | 1 + .../add_completions/add_member_completion.rs | 7 +- .../providers/auto_require_provider.rs | 4 +- .../definition/goto_def_definition.rs | 153 +++++++++++++++++- .../src/handlers/test/definition_test.rs | 46 ++++++ crates/emmylua_ls/src/util/mod.rs | 4 +- .../src/util/module_name_convert.rs | 20 +-- crates/emmylua_parser/src/grammar/doc/tag.rs | 2 +- crates/emmylua_parser/src/grammar/doc/test.rs | 4 +- .../emmylua_parser/src/lexer/lua_doc_lexer.rs | 7 +- 18 files changed, 336 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bf9b3679..15e00b04f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,6 +651,7 @@ dependencies = [ "fern", "glob", "googletest", + "internment", "itertools 0.14.0", "log", "lsp-server", diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index 1adb5e36b..753cfe8af 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -147,7 +147,7 @@ --- Skip partial diagnostics, typically used to optimize diagnostic performance. --- --- Receives a parameter, the options are: ---- - `table_field` - Skip diagnostic for `table` fields +--- - `table_field` - Skip diagnostic for `table` fields. Usually attached to configuration tables that do not require actual diagnostic fields. ---@attribute skip_diagnostic(code: string) --- Index field alias, will be displayed in `hint` and `completion`. @@ -163,3 +163,12 @@ --- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true` --- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true` ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) + +--- Associates `getter` and `setter` methods with a field. Currently provides only definition navigation functionality, +--- and the target methods must reside within the same class. +--- +--- params: +--- - convention: Naming convention, defaults to `camelCase`. Implicitly adds `get` and `set` prefixes. eg: `_age` -> `getAge`, `setAge`. +--- - getter: Getter method name. Takes precedence over `convention`. +--- - setter: Setter method name. Takes precedence over `convention`. +---@attribute field_accessor(convention: "camelCase"|"PascalCase"|"snake_case"|nil, getter: string?, setter: string?) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs index 4b4e479ae..5ae6c9378 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -43,15 +43,47 @@ pub fn infer_attribute_uses( let attribute_uses = tag_use.get_attribute_uses(); let mut result = Vec::new(); for attribute_use in attribute_uses { - let mut params = Vec::new(); - if let Some(attribute_call_arg_list) = attribute_use.get_arg_list() { - for arg in attribute_call_arg_list.get_args() { - let arg_type = infer_attribute_arg_type(arg); - params.push(arg_type); - } - } let attribute_type = infer_type(analyzer, LuaDocType::Name(attribute_use.get_type()?)); if let LuaType::Ref(type_id) = attribute_type { + let arg_types: Vec = attribute_use + .get_arg_list() + .map(|arg_list| arg_list.get_args().map(infer_attribute_arg_type).collect()) + .unwrap_or_default(); + let param_names = analyzer + .db + .get_type_index() + .get_type_decl(&type_id) + .and_then(|decl| decl.get_attribute_type()) + .and_then(|typ| match typ { + LuaType::DocAttribute(attr_type) => Some( + attr_type + .get_params() + .iter() + .map(|(name, _)| name.clone()) + .collect::>(), + ), + _ => None, + }) + .unwrap_or_default(); + + let mut params = Vec::new(); + for (idx, arg_type) in arg_types.into_iter().enumerate() { + let param_name = param_names + .get(idx) + .cloned() + .or_else(|| { + param_names.last().and_then(|last| { + if last == "..." { + Some(last.clone()) + } else { + None + } + }) + }) + .unwrap_or_default(); + params.push((param_name, Some(arg_type))); + } + result.push(LuaAttributeUse::new(type_id, params)); } } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 73230e8d6..3b94faa3a 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -270,9 +270,12 @@ pub fn try_resolve_constructor( .flatten() .find(|attr| attr.id.get_name() == "constructor") .ok_or(InferFailReason::None)?; - let LuaType::DocStringConst(target_signature_name) = - constructor_use.args.first().ok_or(InferFailReason::None)? - else { + let target_signature_type = constructor_use + .args + .first() + .and_then(|(_, typ)| typ.as_ref()) + .ok_or(InferFailReason::None)?; + let LuaType::DocStringConst(target_signature_name) = target_signature_type else { return Err(InferFailReason::None); }; let target_type_decl_id = get_constructor_target_type( @@ -288,20 +291,24 @@ pub fn try_resolve_constructor( let members = find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?; let ctor_signature_member = members.first().ok_or(InferFailReason::None)?; - let strip_self = { - if let Some(LuaType::DocBooleanConst(strip_self)) = constructor_use.args.get(1) { - *strip_self - } else { - true - } - }; - let return_self = { - if let Some(LuaType::DocBooleanConst(return_self)) = constructor_use.args.get(2) { - *return_self - } else { - true - } - }; + let strip_self = constructor_use + .args + .get(1) + .and_then(|(_, typ)| typ.as_ref()) + .and_then(|typ| match typ { + LuaType::DocBooleanConst(value) => Some(*value), + _ => None, + }) + .unwrap_or(true); + let return_self = constructor_use + .args + .get(2) + .and_then(|(_, typ)| typ.as_ref()) + .and_then(|typ| match typ { + LuaType::DocBooleanConst(value) => Some(*value), + _ => None, + }) + .unwrap_or(true); set_signature_to_default_call(db, cache, ctor_signature_member, strip_self, return_self) .ok_or(InferFailReason::None)?; diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index 1030353dc..7a979c79d 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -1,69 +1,6 @@ #[cfg(test)] mod test { - use crate::VirtualWorkspace; - - #[test] - fn test_def_attribute() { - let mut ws = VirtualWorkspace::new(); - ws.def( - r#" - ---@attribute Deprecated(message: string?) - ---@attribute SkipDiagnosticTable() -- 跳过对表的部分诊断, 用于优化性能, 通常来说对巨型配置表使用. - ---@attribute IndexFieldAlias(name: string) -- 索引字段别名, 将在`hint`与`completion`中显示别名. - "#, - ); - - // ws.def( - // r#" - // ---@attribute check_point(x: string, y: number) - // "#, - // ); - - // ws.def( - // r#" - // ---@attribute SkipDiagnosticTable() - - // ---@[SkipDiagnosticTable, Skip] - // local config = {} - // "#, - // ); - - ws.def( - r#" - ---@class A - ---@field a string - ---@[Deprecated] - ---@field b string - "#, - ); - - // ws.def( - // r#" - - // ---@[deprecated] - // local a - // "#, - // ); - } - - #[test] - fn test_attribute_attach() { - let mut ws = VirtualWorkspace::new(); - // ws.def( - // r#" - // ---@generic [attribute] T - // local function f() - // end - // "#, - // ); - ws.def( - r#" - ---@generic [attribute] T - local function f() - end - "#, - ); - } + use crate::{DiagnosticCode, VirtualWorkspace}; #[test] fn test_constructor() { @@ -89,25 +26,18 @@ mod test { "#, ), ]); + } - // ws.def( - // r#" - // ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) - - // ---@generic T - // ---@param [constructor("__init")] name `T` - // ---@return T - // function meta(name) - // end - // "#, - // ); - // ws.def( - // r#" - // A = meta("A") + #[test] + fn test_def_attribute() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); - // function A:__init(a) - // end - // "#, - // ); + ws.check_code_for( + DiagnosticCode::AssignTypeMismatch, + r#" + ---@[skip_diagnostic("table_field")] + local config = {} + "#, + ); } } diff --git a/crates/emmylua_code_analysis/src/db_index/property/property.rs b/crates/emmylua_code_analysis/src/db_index/property/property.rs index 07183d161..a97316e02 100644 --- a/crates/emmylua_code_analysis/src/db_index/property/property.rs +++ b/crates/emmylua_code_analysis/src/db_index/property/property.rs @@ -175,11 +175,18 @@ impl LuaPropertyId { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct LuaAttributeUse { pub id: LuaTypeDeclId, - pub args: Vec, + pub args: Vec<(String, Option)>, } impl LuaAttributeUse { - pub fn new(name: LuaTypeDeclId, args: Vec) -> Self { - Self { id: name, args } + pub fn new(id: LuaTypeDeclId, args: Vec<(String, Option)>) -> Self { + Self { id, args } + } + + pub fn get_param_by_name(&self, name: &str) -> Option<&LuaType> { + self.args + .iter() + .find(|(n, _)| n == name) + .and_then(|(_, typ)| typ.as_ref()) } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs index b020bb23a..eeff42c88 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs @@ -8,8 +8,8 @@ use rowan::{NodeOrToken, TextRange}; use crate::{ DiagnosticCode, LuaDeclExtra, LuaDeclId, LuaMemberKey, LuaSemanticDeclId, LuaType, - SemanticDeclLevel, SemanticModel, TypeCheckFailReason, TypeCheckResult, VariadicType, - infer_index_expr, + LuaTypeDeclId, SemanticDeclLevel, SemanticModel, TypeCheckFailReason, TypeCheckResult, + VariadicType, infer_index_expr, }; use super::{Checker, DiagnosticContext, humanize_lint_type}; @@ -204,6 +204,7 @@ fn check_local_stat( Some(()) } +/// 检查整个表, 返回`true`表示诊断出异常. pub fn check_table_expr( context: &mut DiagnosticContext, semantic_model: &SemanticModel, @@ -218,16 +219,16 @@ pub fn check_table_expr( .get_property_index() .get_property(&semantic_decl) { - if let Some(attribute_uses) = property.attribute_uses() { - for attribute_use in attribute_uses.iter() { - if attribute_use.id.get_name() == "skip_diagnostic" { - if let Some(LuaType::DocStringConst(code)) = attribute_use.args.first() { - if code.as_ref() == "table_field" { - return Some(false); - } - }; + if let Some(skip_diagnostic) = + property.find_attribute_use(LuaTypeDeclId::new("skip_diagnostic")) + { + if let Some(LuaType::DocStringConst(code)) = + skip_diagnostic.get_param_by_name("code") + { + if code.as_ref() == "table_field" { + return Some(false); } - } + }; } } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs index 9ebc7a822..19d9f5063 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs @@ -99,10 +99,11 @@ fn check_deprecated( if let Some(attribute_uses) = property.attribute_uses() { for attribute_use in attribute_uses.iter() { if attribute_use.id.get_name() == "deprecated" { - let deprecated_message = match attribute_use.args.first() { - Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(), - _ => "deprecated".to_string(), - }; + let deprecated_message = + match attribute_use.args.first().and_then(|(_, typ)| typ.as_ref()) { + Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(), + _ => "deprecated".to_string(), + }; context.add_diagnostic(DiagnosticCode::Deprecated, range, deprecated_message, None); } } diff --git a/crates/emmylua_ls/Cargo.toml b/crates/emmylua_ls/Cargo.toml index 8cd8880c3..f560555c2 100644 --- a/crates/emmylua_ls/Cargo.toml +++ b/crates/emmylua_ls/Cargo.toml @@ -39,6 +39,7 @@ serde_yml.workspace = true itertools.workspace = true dirs.workspace = true wax.workspace = true +internment.workspace = true [dependencies.clap] workspace = true diff --git a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs index 6f5ea4b96..39f174341 100644 --- a/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs @@ -363,9 +363,10 @@ pub fn get_index_alias_name( .find_attribute_use(LuaTypeDeclId::new("index_alias"))? .args .first() - .map(|param| match param { - LuaType::DocStringConst(s) => s.as_ref(), - _ => "", + .and_then(|(_, typ)| typ.as_ref()) + .and_then(|param| match param { + LuaType::DocStringConst(s) => Some(s.as_ref()), + _ => None, })? .to_string(); Some(alias_label) diff --git a/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs index e6a3b73cc..51e12aed3 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/auto_require_provider.rs @@ -12,7 +12,7 @@ use crate::{ completion_data::CompletionData, }, }, - util::{flie_name_convert, module_name_convert}, + util::{file_name_convert, module_name_convert}, }; pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { @@ -136,7 +136,7 @@ fn try_add_member_completion_items( LuaType::TableConst(_) | LuaType::Def(_) => { let member_infos = builder.semantic_model.get_member_infos(export_type)?; for member_info in member_infos { - let key_name = flie_name_convert( + let key_name = file_name_convert( &member_info.key.to_path(), &member_info.typ, file_conversion, diff --git a/crates/emmylua_ls/src/handlers/definition/goto_def_definition.rs b/crates/emmylua_ls/src/handlers/definition/goto_def_definition.rs index f75ba937f..2521ebe63 100644 --- a/crates/emmylua_ls/src/handlers/definition/goto_def_definition.rs +++ b/crates/emmylua_ls/src/handlers/definition/goto_def_definition.rs @@ -1,8 +1,8 @@ use std::str::FromStr; use emmylua_code_analysis::{ - LuaCompilation, LuaDeclId, LuaMemberId, LuaMemberInfo, LuaMemberKey, LuaSemanticDeclId, - LuaType, LuaTypeDeclId, SemanticDeclLevel, SemanticModel, + LuaCompilation, LuaDeclId, LuaMemberId, LuaMemberInfo, LuaMemberKey, LuaMemberOwner, + LuaSemanticDeclId, LuaType, LuaTypeDeclId, SemanticDeclLevel, SemanticModel, }; use emmylua_parser::{ LuaAstNode, LuaAstToken, LuaCallExpr, LuaExpr, LuaIndexExpr, LuaReturnStat, LuaStringToken, @@ -11,9 +11,14 @@ use emmylua_parser::{ use itertools::Itertools; use lsp_types::{GotoDefinitionResponse, Location, Position, Range, Uri}; -use crate::handlers::{ - definition::goto_function::{find_function_call_origin, find_matching_function_definitions}, - hover::{find_all_same_named_members, find_member_origin_owner}, +use crate::{ + handlers::{ + definition::goto_function::{ + find_function_call_origin, find_matching_function_definitions, + }, + hover::{find_all_same_named_members, find_member_origin_owner}, + }, + util::{to_camel_case, to_pascal_case, to_snake_case}, }; pub fn goto_def_definition( @@ -104,6 +109,8 @@ fn handle_member_definition( if let LuaSemanticDeclId::Member(member_id) = member && let Some(location) = get_member_location(semantic_model, &member_id) { + // 尝试添加访问器的位置 + try_set_accessor_locations(semantic_model, &member, &mut locations); locations.push(location); } } @@ -421,3 +428,139 @@ fn try_extract_str_tpl_ref_locations( } None } + +fn try_set_accessor_locations( + semantic_model: &SemanticModel, + semantic_decl_id: &LuaSemanticDeclId, + locations: &mut Vec, +) -> Option<()> { + #[derive(Clone, Copy)] + enum AccessorCaseConvention { + CamelCase, // camelCase + SnakeCase, // snake_case + PascalCase, // PascalCase + } + + impl AccessorCaseConvention { + fn build_name(self, prefix: &str, field_name: &str) -> Option { + if field_name.is_empty() { + return None; + } + let full_name = format!("{}_{}", prefix, field_name); + let name = match self { + AccessorCaseConvention::CamelCase => to_camel_case(&full_name), + AccessorCaseConvention::SnakeCase => to_snake_case(&full_name), + AccessorCaseConvention::PascalCase => to_pascal_case(&full_name), + }; + Some(name) + } + } + + let member_id = match semantic_decl_id { + LuaSemanticDeclId::Member(id) => id, + _ => return None, + }; + let current_owner = semantic_model + .get_db() + .get_member_index() + .get_current_owner(member_id)?; + let prefix_type = match current_owner { + LuaMemberOwner::Type(id) => LuaType::Ref(id.clone()), + _ => return None, + }; + let property = semantic_model + .get_db() + .get_property_index() + .get_property(&semantic_decl_id)?; + + let attribute_use = property.find_attribute_use(LuaTypeDeclId::new("field_accessor"))?; + let has_getter = + if let Some(LuaType::DocStringConst(getter)) = attribute_use.get_param_by_name("getter") { + try_add_accessor_location( + semantic_model, + &prefix_type, + getter.as_str().into(), + locations, + ) + } else { + false + }; + let has_setter = + if let Some(LuaType::DocStringConst(setter)) = attribute_use.get_param_by_name("setter") { + try_add_accessor_location( + semantic_model, + &prefix_type, + setter.as_str().into(), + locations, + ) + } else { + false + }; + + // 显式指定了获取器与设置器, 则不需要根据规则处理 + if has_getter && has_setter { + return Some(()); + } + // 根据规则处理 + // "camelCase"|"PascalCase"|"snake_case" + let convention = { + if let Some(LuaType::DocStringConst(convention)) = + attribute_use.get_param_by_name("convention") + { + match convention.as_str() { + "camelCase" => AccessorCaseConvention::CamelCase, + "snake_case" => AccessorCaseConvention::SnakeCase, + "PascalCase" => AccessorCaseConvention::PascalCase, + _ => AccessorCaseConvention::CamelCase, + } + } else { + AccessorCaseConvention::CamelCase + } + }; + + let Some(original_name) = semantic_model + .get_db() + .get_member_index() + .get_member(member_id)? + .get_key() + .get_name() + else { + return Some(()); + }; + + if !has_getter { + if let Some(getter_name) = convention.build_name("get", original_name) { + try_add_accessor_location(semantic_model, &prefix_type, getter_name, locations); + } + } + + if !has_setter { + if let Some(setter_name) = convention.build_name("set", original_name) { + try_add_accessor_location(semantic_model, &prefix_type, setter_name, locations); + } + } + + Some(()) +} + +/// 尝试添加访问器位置到位置列表中 +fn try_add_accessor_location( + semantic_model: &SemanticModel, + prefix_type: &LuaType, + accessor_name: String, + locations: &mut Vec, +) -> bool { + let accessor_key = LuaMemberKey::Name(accessor_name.as_str().into()); + if let Some(member_infos) = + semantic_model.get_member_info_with_key(prefix_type, accessor_key, false) + { + if let Some(member_info) = member_infos.first() + && let Some(LuaSemanticDeclId::Member(accessor_id)) = member_info.property_owner_id + && let Some(location) = get_member_location(semantic_model, &accessor_id) + { + locations.push(location); + return true; + } + } + false +} diff --git a/crates/emmylua_ls/src/handlers/test/definition_test.rs b/crates/emmylua_ls/src/handlers/test/definition_test.rs index e7a0e6a45..3a59361a2 100644 --- a/crates/emmylua_ls/src/handlers/test/definition_test.rs +++ b/crates/emmylua_ls/src/handlers/test/definition_test.rs @@ -508,4 +508,50 @@ mod tests { Ok(()) } + + #[gtest] + fn test_accessors() -> Result<()> { + let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib(); + + ws.def( + r#" + ---@class A + ---@[field_accessor] + ---@field age number + local A + + ---@private + function A:getAge() + end + + ---@private + function A:setAge(value) + end + "#, + ); + + check!(ws.check_definition( + r#" + ---@type A + local obj + obj.age = 1 + "#, + vec![ + Expected { + file: "".to_string(), + line: 3, + }, + Expected { + file: "".to_string(), + line: 7, + }, + Expected { + file: "".to_string(), + line: 11, + } + ], + )); + + Ok(()) + } } diff --git a/crates/emmylua_ls/src/util/mod.rs b/crates/emmylua_ls/src/util/mod.rs index af6f80a71..ddc8bfbfa 100644 --- a/crates/emmylua_ls/src/util/mod.rs +++ b/crates/emmylua_ls/src/util/mod.rs @@ -3,5 +3,7 @@ mod module_name_convert; mod time_cancel_token; pub use desc::*; -pub use module_name_convert::{flie_name_convert, module_name_convert}; +pub use module_name_convert::{ + file_name_convert, module_name_convert, to_camel_case, to_pascal_case, to_snake_case, +}; pub use time_cancel_token::time_cancel_token; diff --git a/crates/emmylua_ls/src/util/module_name_convert.rs b/crates/emmylua_ls/src/util/module_name_convert.rs index 3ff29a1be..0f61e283b 100644 --- a/crates/emmylua_ls/src/util/module_name_convert.rs +++ b/crates/emmylua_ls/src/util/module_name_convert.rs @@ -29,33 +29,33 @@ pub fn module_name_convert( module_name } -pub fn flie_name_convert( +pub fn file_name_convert( key: &str, typ: &LuaType, file_conversion: EmmyrcFilenameConvention, ) -> String { - let mut flie_name = key.to_string(); + let mut file_name = key.to_string(); match file_conversion { EmmyrcFilenameConvention::SnakeCase => { - flie_name = to_snake_case(&flie_name); + file_name = to_snake_case(&file_name); } EmmyrcFilenameConvention::CamelCase => { - flie_name = to_camel_case(&flie_name); + file_name = to_camel_case(&file_name); } EmmyrcFilenameConvention::PascalCase => { - flie_name = to_pascal_case(&flie_name); + file_name = to_pascal_case(&file_name); } EmmyrcFilenameConvention::Keep => {} EmmyrcFilenameConvention::KeepClass => { if let LuaType::Def(id) = typ { - flie_name = id.get_simple_name().to_string(); + file_name = id.get_simple_name().to_string(); } } } - flie_name + file_name } -fn to_snake_case(s: &str) -> String { +pub fn to_snake_case(s: &str) -> String { let mut result = String::new(); for (i, ch) in s.chars().enumerate() { if ch.is_uppercase() && i != 0 { @@ -68,7 +68,7 @@ fn to_snake_case(s: &str) -> String { result } -fn to_camel_case(s: &str) -> String { +pub fn to_camel_case(s: &str) -> String { let mut result = String::new(); let mut next_uppercase = false; for (i, ch) in s.chars().enumerate() { @@ -86,7 +86,7 @@ fn to_camel_case(s: &str) -> String { result } -fn to_pascal_case(s: &str) -> String { +pub fn to_pascal_case(s: &str) -> String { let mut result = String::new(); let mut next_uppercase = true; for ch in s.chars() { diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index c690e2766..e591ad8a6 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -771,7 +771,7 @@ fn parse_attribute_arg_list(p: &mut LuaDocParser) -> DocParseResult { fn parse_attribute_arg(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::LiteralExpr); - // TODO: 添加具名参数支持(name = value) + // TODO: 添加具名参数支持(name: value) match p.current_token() { LuaTokenKind::TkInt | LuaTokenKind::TkFloat diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 2da1eb9fe..4bd80f225 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2943,9 +2943,9 @@ Syntax(Chunk)@0..105 ---@param [attribute] a number ---@return [attribute] number, [attribute] string "#; - print_ast(code); + // print_ast(code); // print_ast(r#" - // ---@class A<[attribute] T, [attribute] R> + // ---@[field_accessor(nil)] // "#); let result = r#" Syntax(Chunk)@0..155 diff --git a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs index 9d770299e..09dea3ed8 100644 --- a/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs +++ b/crates/emmylua_parser/src/lexer/lua_doc_lexer.rs @@ -638,7 +638,12 @@ impl LuaDocLexer<'_> { ch if is_name_start(ch) => { reader.bump(); reader.eat_while(is_name_continue); - LuaTokenKind::TkName + let text = reader.current_text(); + if text == "nil" { + LuaTokenKind::TkNil + } else { + LuaTokenKind::TkName + } } _ => { reader.bump(); From f193a6070d450889ef605b46808a44cf7525da7c Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 16 Oct 2025 13:46:50 +0800 Subject: [PATCH 28/30] update attribute `constructor` --- .../resources/std/builtin.lua | 7 +- .../src/compilation/analyzer/lua/call.rs | 26 ++-- .../compilation/analyzer/unresolve/resolve.rs | 120 +++++++++++------- .../src/db_index/signature/signature.rs | 9 ++ .../src/handlers/test/inlay_hint_test.rs | 2 +- 5 files changed, 102 insertions(+), 62 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index 753cfe8af..b77c0274c 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -159,15 +159,16 @@ --- used to specify the default constructor of a class. --- --- Parameters: ---- - `name` - The name of the constructor +--- - `name` - The name of the method as a constructor. +--- - `root_class` - Used to mark the root class, will implicitly inherit this class, such as `System.Object` in c#. Defaults to empty. --- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true` --- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true` ----@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) +---@attribute constructor(name: string, root_class: string?, strip_self: boolean?, return_self: boolean?) --- Associates `getter` and `setter` methods with a field. Currently provides only definition navigation functionality, --- and the target methods must reside within the same class. --- ---- params: +--- Parameters: --- - convention: Naming convention, defaults to `camelCase`. Implicitly adds `get` and `set` prefixes. eg: `_age` -> `getAge`, `setAge`. --- - getter: Getter method name. Takes precedence over `convention`. --- - setter: Setter method name. Takes precedence over `convention`. diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs index f433d5749..9fb6d3834 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/call.rs @@ -13,21 +13,17 @@ pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Optio }; let signature = analyzer.db.get_signature_index().get(&signature_id)?; for (idx, param_info) in signature.param_docs.iter() { - if let Some(ref attrs) = param_info.attributes { - for attr in attrs.iter() { - if attr.id.get_name() == "constructor" { - let unresolve = UnResolveConstructor { - file_id: analyzer.file_id, - call_expr: call_expr.clone(), - signature_id, - param_idx: *idx, - }; - analyzer - .context - .add_unresolve(unresolve.into(), InferFailReason::None); - return Some(()); - } - } + if param_info.get_attribute_by_name("constructor").is_some() { + let unresolve = UnResolveConstructor { + file_id: analyzer.file_id, + call_expr: call_expr.clone(), + signature_id, + param_idx: *idx, + }; + analyzer + .context + .add_unresolve(unresolve.into(), InferFailReason::None); + return Some(()); } } } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 3b94faa3a..9436b3f01 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -257,58 +257,92 @@ pub fn try_resolve_constructor( cache: &mut LuaInferCache, unresolve_constructor: &mut UnResolveConstructor, ) -> ResolveResult { - let signature = db - .get_signature_index() - .get(&unresolve_constructor.signature_id) - .ok_or(InferFailReason::None)?; - let param_info = signature - .get_param_info_by_id(unresolve_constructor.param_idx) - .ok_or(InferFailReason::None)?; - let constructor_use = param_info - .attributes - .iter() - .flatten() - .find(|attr| attr.id.get_name() == "constructor") - .ok_or(InferFailReason::None)?; - let target_signature_type = constructor_use - .args - .first() - .and_then(|(_, typ)| typ.as_ref()) - .ok_or(InferFailReason::None)?; - let LuaType::DocStringConst(target_signature_name) = target_signature_type else { - return Err(InferFailReason::None); - }; - let target_type_decl_id = get_constructor_target_type( + let (param_type, target_signature_name, root_class, strip_self, return_self) = { + let signature = db + .get_signature_index() + .get(&unresolve_constructor.signature_id) + .ok_or(InferFailReason::None)?; + let param_info = signature + .get_param_info_by_id(unresolve_constructor.param_idx) + .ok_or(InferFailReason::None)?; + let constructor_use = param_info + .get_attribute_by_name("constructor") + .ok_or(InferFailReason::None)?; + + // 作为构造函数的方法名 + let target_signature_name = constructor_use + .get_param_by_name("name") + .and_then(|typ| match typ { + LuaType::DocStringConst(value) => Some(value.deref().clone()), + _ => None, + }) + .ok_or(InferFailReason::None)?; + // 作为构造函数的根类 + let root_class = + constructor_use + .get_param_by_name("root_class") + .and_then(|typ| match typ { + LuaType::DocStringConst(value) => Some(value.deref().clone()), + _ => None, + }); + // 是否可以省略self参数 + let strip_self = constructor_use + .get_param_by_name("strip_self") + .and_then(|typ| match typ { + LuaType::DocBooleanConst(value) => Some(*value), + _ => None, + }) + .unwrap_or(true); + // 是否返回self + let return_self = constructor_use + .get_param_by_name("return_self") + .and_then(|typ| match typ { + LuaType::DocBooleanConst(value) => Some(*value), + _ => None, + }) + .unwrap_or(true); + + Ok::<_, InferFailReason>(( + param_info.type_ref.clone(), + target_signature_name, + root_class, + strip_self, + return_self, + )) + }?; + + // 需要添加构造函数的目标类型 + let target_id = get_constructor_target_type( db, cache, - ¶m_info.type_ref, + ¶m_type, unresolve_constructor.call_expr.clone(), unresolve_constructor.param_idx, ) .ok_or(InferFailReason::None)?; - let target_type = LuaType::Ref(target_type_decl_id); - let member_key = LuaMemberKey::Name(target_signature_name.deref().clone()); + + // 添加根类 + if let Some(root_class) = root_class { + let root_type_id = LuaTypeDeclId::new(&root_class); + if let Some(type_decl) = db.get_type_index().get_type_decl(&root_type_id) { + if type_decl.is_class() { + let root_type = LuaType::Ref(root_type_id.clone()); + db.get_type_index_mut().add_super_type( + target_id.clone(), + unresolve_constructor.file_id, + root_type, + ); + } + } + } + + // 添加构造函数 + let target_type = LuaType::Ref(target_id); + let member_key = LuaMemberKey::Name(target_signature_name); let members = find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?; let ctor_signature_member = members.first().ok_or(InferFailReason::None)?; - let strip_self = constructor_use - .args - .get(1) - .and_then(|(_, typ)| typ.as_ref()) - .and_then(|typ| match typ { - LuaType::DocBooleanConst(value) => Some(*value), - _ => None, - }) - .unwrap_or(true); - let return_self = constructor_use - .args - .get(2) - .and_then(|(_, typ)| typ.as_ref()) - .and_then(|typ| match typ { - LuaType::DocBooleanConst(value) => Some(*value), - _ => None, - }) - .unwrap_or(true); + set_signature_to_default_call(db, cache, ctor_signature_member, strip_self, return_self) .ok_or(InferFailReason::None)?; diff --git a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs index 347b6664e..cf1b983af 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs @@ -185,6 +185,15 @@ pub struct LuaDocParamInfo { pub attributes: Option>, } +impl LuaDocParamInfo { + pub fn get_attribute_by_name(&self, name: &str) -> Option<&LuaAttributeUse> { + self.attributes + .iter() + .flatten() + .find(|attr| attr.id.get_name() == name) + } +} + #[derive(Debug)] pub struct LuaDocReturnInfo { pub name: Option, diff --git a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs index 90c09534e..44bb0c1b5 100644 --- a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs +++ b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs @@ -136,7 +136,7 @@ mod tests { #[gtest] fn test_class_call_hint() -> Result<()> { - let mut ws = ProviderVirtualWorkspace::new(); + let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib(); ws.def( r#" ---@generic T From a737ce96fe4b9ba722d4985f115a4d8151e03d42 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 20 Oct 2025 17:03:32 +0800 Subject: [PATCH 29/30] rename attribute `skip_diagnostic` to `lsp_perf_optim` --- CHANGELOG.md | 30 +++++++++++++++---- .../resources/std/builtin.lua | 6 ++-- .../src/compilation/test/attribute_test.rs | 2 +- .../checker/assign_type_mismatch.rs | 8 ++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6412ac43..cd1cad3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,27 +3,47 @@ *All notable changes to the EmmyLua Analyzer Rust project will be documented in this file.* --- -## [0.15.0] - Unreleased +## [0.17.0] - Unreleased ### 🔧 Changed - **Refactor IndexAliasName**: 删除原先的索引别名实现(`-- [IndexAliasName]`), 现在使用`---@[index_alias("name")]` +- **Refactor ClassDefaultCall**: 删除配置项`runtime.class_default_call`, 转为使用`---@[constructor("")]` ### ✨ Added -- **Attribute**: 实现了新的特性`---@attribute`,用于定义附加元数据,内置三个特性: +- **Attribute**: 实现了新的特性`---@attribute`,用于定义附加元数据,内置多个特性: ```lua --- Deprecated. Receives an optional message parameter. ---@attribute deprecated(message: string?) ---- Skip partial diagnostics, typically used to optimize diagnostic performance. +--- Language Server Performance Optimization Items. --- --- Receives a parameter, the options are: ---- - `table_field` - Skip diagnostic for `table` fields ----@attribute skip_diagnostic(code: string) +--- - `check_table_field` - Skip the assign check for table fields. It is recommended to use this option for all large configuration tables. +---@attribute lsp_perf_optim(code: "check_table_field"|string) --- Index field alias, will be displayed in `hint` and `completion`. --- --- Receives a string parameter for the alias name. ---@attribute index_alias(name: string) + +--- This attribute must be applied to function parameters, and the function parameter's type must be a string template generic, +--- used to specify the default constructor of a class. +--- +--- Parameters: +--- - `name` - The name of the method as a constructor. +--- - `root_class` - Used to mark the root class, will implicitly inherit this class, such as `System.Object` in c#. Defaults to empty. +--- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true` +--- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true` +---@attribute constructor(name: string, root_class: string?, strip_self: boolean?, return_self: boolean?) + +--- Associates `getter` and `setter` methods with a field. Currently provides only definition navigation functionality, +--- and the target methods must reside within the same class. +--- +--- Parameters: +--- - convention: Naming convention, defaults to `camelCase`. Implicitly adds `get` and `set` prefixes. eg: `_age` -> `getAge`, `setAge`. +--- - getter: Getter method name. Takes precedence over `convention`. +--- - setter: Setter method name. Takes precedence over `convention`. +---@attribute field_accessor(convention: "camelCase"|"PascalCase"|"snake_case"|nil, getter: string?, setter: string?) ``` 使用语法为 `---@[attribute_name_1(arg...), attribute_name_2(arg...), ...]`, 可以同时使用多个特性, 示例: diff --git a/crates/emmylua_code_analysis/resources/std/builtin.lua b/crates/emmylua_code_analysis/resources/std/builtin.lua index b77c0274c..3bb4ea17c 100644 --- a/crates/emmylua_code_analysis/resources/std/builtin.lua +++ b/crates/emmylua_code_analysis/resources/std/builtin.lua @@ -144,11 +144,11 @@ --- Deprecated. Receives an optional message parameter. ---@attribute deprecated(message: string?) ---- Skip partial diagnostics, typically used to optimize diagnostic performance. +--- Language Server Performance Optimization Items. --- --- Receives a parameter, the options are: ---- - `table_field` - Skip diagnostic for `table` fields. Usually attached to configuration tables that do not require actual diagnostic fields. ----@attribute skip_diagnostic(code: string) +--- - `check_table_field` - Skip the assign check for table fields. It is recommended to use this option for all large configuration tables. +---@attribute lsp_perf_optim(code: "check_table_field"|string) --- Index field alias, will be displayed in `hint` and `completion`. --- diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index 7a979c79d..72d204ef4 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -35,7 +35,7 @@ mod test { ws.check_code_for( DiagnosticCode::AssignTypeMismatch, r#" - ---@[skip_diagnostic("table_field")] + ---@[lsp_perf_optim("check_table_field")] local config = {} "#, ); diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs index eeff42c88..dbeb34e0f 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs @@ -219,13 +219,13 @@ pub fn check_table_expr( .get_property_index() .get_property(&semantic_decl) { - if let Some(skip_diagnostic) = - property.find_attribute_use(LuaTypeDeclId::new("skip_diagnostic")) + if let Some(lsp_perf_optim) = + property.find_attribute_use(LuaTypeDeclId::new("lsp_perf_optim")) { if let Some(LuaType::DocStringConst(code)) = - skip_diagnostic.get_param_by_name("code") + lsp_perf_optim.get_param_by_name("code") { - if code.as_ref() == "table_field" { + if code.as_ref() == "check_table_field" { return Some(false); } }; From fbd05b1e756a60b903fda427038876695f33ae64 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 21 Oct 2025 00:10:58 +0800 Subject: [PATCH 30/30] =?UTF-8?q?attribute:=20=E7=A7=BB=E9=99=A4=E5=86=85?= =?UTF-8?q?=E5=B5=8C=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analyzer/doc/attribute_tags.rs | 85 ++++++++++++++--- .../compilation/analyzer/doc/type_def_tags.rs | 19 +--- .../compilation/analyzer/doc/type_ref_tags.rs | 34 +++---- .../src/compilation/test/annotation_test.rs | 3 +- .../src/compilation/test/attribute_test.rs | 5 +- .../src/compilation/test/overload_test.rs | 8 +- .../semantic_token/build_semantic_tokens.rs | 2 +- .../src/handlers/test/inlay_hint_test.rs | 3 +- crates/emmylua_parser/src/grammar/doc/tag.rs | 20 +--- crates/emmylua_parser/src/grammar/doc/test.rs | 91 ------------------- .../emmylua_parser/src/syntax/node/doc/mod.rs | 4 - .../emmylua_parser/src/syntax/node/doc/tag.rs | 22 +---- 12 files changed, 111 insertions(+), 185 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs index 5ae6c9378..c02591689 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs @@ -1,11 +1,11 @@ use emmylua_parser::{ LuaAst, LuaAstNode, LuaDocTagAttributeUse, LuaDocType, LuaExpr, LuaKind, LuaLiteralExpr, - LuaLiteralToken, LuaSyntaxKind, LuaSyntaxNode, + LuaLiteralToken, LuaSyntaxKind, LuaSyntaxNode, LuaTokenKind, }; use smol_str::SmolStr; use crate::{ - LuaAttributeUse, LuaType, + LuaAttributeUse, LuaSemanticDeclId, LuaType, compilation::analyzer::doc::{ DocAnalyzer, infer_type::infer_type, @@ -18,13 +18,26 @@ pub fn analyze_tag_attribute_use( tag_use: LuaDocTagAttributeUse, ) -> Option<()> { let owner = attribute_use_get_owner(analyzer, &tag_use); - let owner_id = match get_owner_id(analyzer, owner, true) { + let owner_id = match get_owner_id(analyzer, owner.clone(), true) { Some(id) => id, None => { report_orphan_tag(analyzer, &tag_use); return None; } }; + + if let Some(owner) = owner { + match (owner, &owner_id) { + (LuaAst::LuaDocTagParam(_), LuaSemanticDeclId::Signature(_)) => { + return Some(()); + } + (LuaAst::LuaDocTagReturn(_), LuaSemanticDeclId::Signature(_)) => { + return Some(()); + } + _ => {} + } + } + let attribute_uses = infer_attribute_uses(analyzer, tag_use)?; for attribute_use in attribute_uses { analyzer.db.get_property_index_mut().add_attribute_use( @@ -107,7 +120,6 @@ fn infer_attribute_arg_type(expr: LuaLiteralExpr) -> LuaType { return LuaType::DocBooleanConst(bool_token.is_true()); } LuaLiteralToken::Nil(_) => return LuaType::Nil, - // todo LuaLiteralToken::Dots(_) => return LuaType::Any, LuaLiteralToken::Question(_) => return LuaType::Nil, } @@ -115,27 +127,31 @@ fn infer_attribute_arg_type(expr: LuaLiteralExpr) -> LuaType { LuaType::Unknown } -/// 特性的寻找所有者需要特殊处理 +/// 寻找特性的所有者 fn attribute_use_get_owner( analyzer: &mut DocAnalyzer, attribute_use: &LuaDocTagAttributeUse, ) -> Option { - // 针对 ---@field 特殊处理 - if let Some(attached_node) = attribute_find_doc_field(&attribute_use.syntax()) { + if let Some(attached_node) = attribute_find_doc(&attribute_use.syntax()) { return LuaAst::cast(attached_node); } - // 回退 analyzer.comment.get_owner() } -fn attribute_find_doc_field(comment: &LuaSyntaxNode) -> Option { - let mut next_sibling = comment.next_sibling(); +fn attribute_find_doc(comment: &LuaSyntaxNode) -> Option { + let mut next_sibling = comment.next_sibling_or_token(); loop { next_sibling.as_ref()?; if let Some(sibling) = &next_sibling { match sibling.kind() { - LuaKind::Syntax(LuaSyntaxKind::DocTagField) => { - return Some(sibling.clone()); + LuaKind::Syntax( + LuaSyntaxKind::DocTagField + | LuaSyntaxKind::DocTagParam + | LuaSyntaxKind::DocTagReturn, + ) => { + if let Some(node) = sibling.as_node() { + return Some(node.clone()); + } } LuaKind::Syntax(LuaSyntaxKind::Comment) => { return None; @@ -149,7 +165,50 @@ fn attribute_find_doc_field(comment: &LuaSyntaxNode) -> Option { } } } - next_sibling = sibling.next_sibling(); + next_sibling = sibling.next_sibling_or_token(); } } } + +fn find_up_attribute( + comment: &LuaSyntaxNode, + result: &mut Vec, + stop_by_continue: bool, +) -> Option<()> { + let mut next_sibling = comment.prev_sibling_or_token(); + loop { + next_sibling.as_ref()?; + if let Some(sibling) = &next_sibling { + match sibling.kind() { + LuaKind::Syntax(LuaSyntaxKind::DocTagAttributeUse) => { + if let Some(node) = sibling.as_node() { + if let Some(node) = LuaDocTagAttributeUse::cast(node.clone()) { + result.push(node); + } + } + } + // 某些情况下我们需要以 --- 为分割中断寻找 + LuaKind::Token(LuaTokenKind::TkDocContinue) => { + if stop_by_continue { + return None; + } + } + LuaKind::Syntax(LuaSyntaxKind::DocDescription) => {} + LuaKind::Syntax(_) => { + return None; + } + _ => {} + } + next_sibling = sibling.prev_sibling_or_token(); + } + } +} + +pub fn find_attach_attribute(ast: LuaAst) -> Option> { + if let LuaAst::LuaDocTagParam(param) = ast { + let mut result = Vec::new(); + find_up_attribute(param.syntax(), &mut result, true); + return Some(result); + } + None +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs index 2b9c6c35f..bed84ac31 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs @@ -11,7 +11,6 @@ use super::{ DocAnalyzer, infer_type::infer_type, preprocess_description, tags::find_owner_closure, }; use crate::GenericParam; -use crate::compilation::analyzer::doc::attribute_tags::infer_attribute_uses; use crate::compilation::analyzer::doc::tags::report_orphan_tag; use crate::{ LuaTypeCache, LuaTypeDeclId, @@ -215,17 +214,12 @@ fn get_generic_params( } else { continue; }; - let attributes = if let Some(attribute_use) = param.get_tag_attribute_use() { - infer_attribute_uses(analyzer, attribute_use) - } else { - None - }; let type_ref = param .get_type() .map(|type_ref| infer_type(analyzer, type_ref)); let is_variadic = param.is_variadic(); - params_result.push(GenericParam::new(name, type_ref, is_variadic, attributes)); + params_result.push(GenericParam::new(name, type_ref, is_variadic, None)); } params_result @@ -360,20 +354,13 @@ pub fn analyze_func_generic(analyzer: &mut DocAnalyzer, tag: LuaDocTagGeneric) - .get_type() .map(|type_ref| infer_type(analyzer, type_ref)); - let attributes = if let Some(attribute_use) = param.get_tag_attribute_use() { - infer_attribute_uses(analyzer, attribute_use) - } else { - None - }; params_result.push(GenericParam::new( SmolStr::new(name.as_str()), type_ref.clone(), false, - attributes.clone(), + None, )); - param_info.push(Arc::new(LuaGenericParamInfo::new( - name, type_ref, attributes, - ))); + param_info.push(Arc::new(LuaGenericParamInfo::new(name, type_ref, None))); } } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs index 02aabc117..583bd49dd 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs @@ -11,10 +11,6 @@ use super::{ preprocess_description, tags::{find_owner_closure, get_owner_id_or_report}, }; -use crate::compilation::analyzer::doc::{ - attribute_tags::infer_attribute_uses, - tags::{find_owner_closure_or_report, get_owner_id, report_orphan_tag}, -}; use crate::{ InFiled, InferFailReason, LuaOperatorMetaMethod, LuaTypeCache, LuaTypeOwner, OperatorFunction, SignatureReturnStatus, TypeOps, @@ -24,6 +20,13 @@ use crate::{ LuaSignatureId, LuaType, }, }; +use crate::{ + LuaAttributeUse, + compilation::analyzer::doc::{ + attribute_tags::{find_attach_attribute, infer_attribute_uses}, + tags::{find_owner_closure_or_report, get_owner_id, report_orphan_tag}, + }, +}; pub fn analyze_type(analyzer: &mut DocAnalyzer, tag: LuaDocTagType) -> Option<()> { let description = tag @@ -188,11 +191,15 @@ pub fn analyze_param(analyzer: &mut DocAnalyzer, tag: LuaDocTagParam) -> Option< if let Some(closure) = find_owner_closure(analyzer) { let id = LuaSignatureId::from_closure(analyzer.file_id, &closure); // 绑定`attribute`标记 - let attributes = if let Some(attribute_use) = tag.get_tag_attribute_use() { - infer_attribute_uses(analyzer, attribute_use) - } else { - None - }; + let attributes = + find_attach_attribute(LuaAst::LuaDocTagParam(tag)).and_then(|tag_attribute_uses| { + let result: Vec = tag_attribute_uses + .into_iter() + .filter_map(|tag_use| infer_attribute_uses(analyzer, tag_use)) + .flatten() + .collect(); + (!result.is_empty()).then_some(result) + }); let signature = analyzer.db.get_signature_index_mut().get_or_create(id); let param_info = LuaDocParamInfo { @@ -235,20 +242,15 @@ pub fn analyze_return(analyzer: &mut DocAnalyzer, tag: LuaDocTagReturn) -> Optio if let Some(closure) = find_owner_closure_or_report(analyzer, &tag) { let signature_id = LuaSignatureId::from_closure(analyzer.file_id, &closure); let returns = tag.get_info_list(); - for (doc_type, name_token, attribute_use_tag) in returns { + for (doc_type, name_token) in returns { let name = name_token.map(|name| name.get_name_text().to_string()); let type_ref = infer_type(analyzer, doc_type); - let attributes = if let Some(attribute) = attribute_use_tag { - infer_attribute_uses(analyzer, attribute) - } else { - None - }; let return_info = LuaDocReturnInfo { name, type_ref, description: description.clone(), - attributes, + attributes: None, }; let signature = analyzer diff --git a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs index d374f770b..65c5c0cae 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/annotation_test.rs @@ -131,7 +131,8 @@ mod test { ws.def( r#" ---@generic T - ---@param [constructor("__init")] name `T` + ---@[constructor("__init")] + ---@param name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs index 72d204ef4..bd0b4e724 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs @@ -16,10 +16,11 @@ mod test { ( "meta.lua", r#" - ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) + ---@attribute constructor(name: string, root_class: string?, strip_self: boolean?, return_self: boolean?) ---@generic T - ---@param [constructor("__init")] name `T` + ---@[constructor("__init")] + ---@param name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs index 1c984d87e..961533a2c 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/overload_test.rs @@ -28,14 +28,14 @@ mod test { } #[test] - fn test_class_default_call() { - let mut ws = VirtualWorkspace::new(); + fn test_class_default_constructor() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); ws.def( r#" - ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?) ---@generic T - ---@param [constructor("__init")] name `T` + ---@[constructor("__init")] + ---@param name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs index 6c2e0fe1f..2f20b10d3 100644 --- a/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs +++ b/crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs @@ -373,7 +373,7 @@ fn build_node_semantic_token( } LuaAst::LuaDocTagReturn(doc_return) => { let type_name_list = doc_return.get_info_list(); - for (_, name, _) in type_name_list { + for (_, name) in type_name_list { if let Some(name) = name { builder.push(name.syntax(), SemanticTokenType::VARIABLE); } diff --git a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs index 44bb0c1b5..4f62af542 100644 --- a/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs +++ b/crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs @@ -140,7 +140,8 @@ mod tests { ws.def( r#" ---@generic T - ---@param [constructor("__init")] name `T` + ---@[constructor("__init")] + ---@param name `T` ---@return T function meta(name) end diff --git a/crates/emmylua_parser/src/grammar/doc/tag.rs b/crates/emmylua_parser/src/grammar/doc/tag.rs index 5caa0aacb..c884a5941 100644 --- a/crates/emmylua_parser/src/grammar/doc/tag.rs +++ b/crates/emmylua_parser/src/grammar/doc/tag.rs @@ -142,10 +142,6 @@ fn parse_generic_decl_list(p: &mut LuaDocParser, allow_angle_brackets: bool) -> // A ... : type fn parse_generic_param(p: &mut LuaDocParser) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocGenericParameter); - // 允许泛型附带特性 - if p.current_token() == LuaTokenKind::TkLeftBracket { - parse_tag_attribute_use(p, false)?; - } expect_token(p, LuaTokenKind::TkName)?; if p.current_token() == LuaTokenKind::TkDots { p.bump(); @@ -311,9 +307,6 @@ fn parse_tag_param(p: &mut LuaDocParser) -> DocParseResult { p.set_state(LuaDocLexerState::Normal); let m = p.mark(LuaSyntaxKind::DocTagParam); p.bump(); - if p.current_token() == LuaTokenKind::TkLeftBracket { - parse_tag_attribute_use(p, false)?; - } if matches!( p.current_token(), LuaTokenKind::TkName | LuaTokenKind::TkDots @@ -341,14 +334,10 @@ fn parse_tag_param(p: &mut LuaDocParser) -> DocParseResult { // ---@return number // ---@return number, string // ---@return number , this just compact luals -// ---@return [attribute] number fn parse_tag_return(p: &mut LuaDocParser) -> DocParseResult { p.set_state(LuaDocLexerState::Normal); let m = p.mark(LuaSyntaxKind::DocTagReturn); p.bump(); - if p.current_token() == LuaTokenKind::TkLeftBracket { - parse_tag_attribute_use(p, false)?; - } parse_type(p)?; @@ -356,9 +345,6 @@ fn parse_tag_return(p: &mut LuaDocParser) -> DocParseResult { while p.current_token() == LuaTokenKind::TkComma { p.bump(); - if p.current_token() == LuaTokenKind::TkLeftBracket { - parse_tag_attribute_use(p, false)?; - } parse_type(p)?; if_token_bump(p, LuaTokenKind::TkName); } @@ -704,9 +690,9 @@ fn parse_type_attribute(p: &mut LuaDocParser) -> DocParseResult { Ok(m.complete(p)) } -// ---@[a(arg1, arg2, ...)] -// ---@[a] -// ---@[a, b, ...] +// ---@[attribute(arg1, arg2, ...)] +// ---@[attribute] +// ---@[attribute1, attribute2, ...] // ---@generic [attribute] T pub fn parse_tag_attribute_use(p: &mut LuaDocParser, allow_description: bool) -> DocParseResult { let m = p.mark(LuaSyntaxKind::DocTagAttributeUse); diff --git a/crates/emmylua_parser/src/grammar/doc/test.rs b/crates/emmylua_parser/src/grammar/doc/test.rs index 4bd80f225..b1add228a 100644 --- a/crates/emmylua_parser/src/grammar/doc/test.rs +++ b/crates/emmylua_parser/src/grammar/doc/test.rs @@ -2935,95 +2935,4 @@ Syntax(Chunk)@0..105 "#; assert_ast_eq!(code, result); } - - #[test] - fn test_attribute_embedded() { - let code = r#" - ---@generic [attribute] T, [attribute] R - ---@param [attribute] a number - ---@return [attribute] number, [attribute] string - "#; - // print_ast(code); - // print_ast(r#" - // ---@[field_accessor(nil)] - // "#); - let result = r#" -Syntax(Chunk)@0..155 - Syntax(Block)@0..155 - Token(TkEndOfLine)@0..1 "\n" - Token(TkWhitespace)@1..9 " " - Syntax(Comment)@9..146 - Token(TkDocStart)@9..13 "---@" - Syntax(DocTagGeneric)@13..49 - Token(TkTagGeneric)@13..20 "generic" - Token(TkWhitespace)@20..21 " " - Syntax(DocGenericDeclareList)@21..49 - Syntax(DocGenericParameter)@21..34 - Syntax(DocTagAttributeUse)@21..32 - Token(TkLeftBracket)@21..22 "[" - Syntax(DocAttributeUse)@22..31 - Syntax(TypeName)@22..31 - Token(TkName)@22..31 "attribute" - Token(TkRightBracket)@31..32 "]" - Token(TkWhitespace)@32..33 " " - Token(TkName)@33..34 "T" - Token(TkComma)@34..35 "," - Token(TkWhitespace)@35..36 " " - Syntax(DocGenericParameter)@36..49 - Syntax(DocTagAttributeUse)@36..47 - Token(TkLeftBracket)@36..37 "[" - Syntax(DocAttributeUse)@37..46 - Syntax(TypeName)@37..46 - Token(TkName)@37..46 "attribute" - Token(TkRightBracket)@46..47 "]" - Token(TkWhitespace)@47..48 " " - Token(TkName)@48..49 "R" - Token(TkEndOfLine)@49..50 "\n" - Token(TkWhitespace)@50..58 " " - Token(TkDocStart)@58..62 "---@" - Syntax(DocTagParam)@62..88 - Token(TkTagParam)@62..67 "param" - Token(TkWhitespace)@67..68 " " - Syntax(DocTagAttributeUse)@68..79 - Token(TkLeftBracket)@68..69 "[" - Syntax(DocAttributeUse)@69..78 - Syntax(TypeName)@69..78 - Token(TkName)@69..78 "attribute" - Token(TkRightBracket)@78..79 "]" - Token(TkWhitespace)@79..80 " " - Token(TkName)@80..81 "a" - Token(TkWhitespace)@81..82 " " - Syntax(TypeName)@82..88 - Token(TkName)@82..88 "number" - Token(TkEndOfLine)@88..89 "\n" - Token(TkWhitespace)@89..97 " " - Token(TkDocStart)@97..101 "---@" - Syntax(DocTagReturn)@101..146 - Token(TkTagReturn)@101..107 "return" - Token(TkWhitespace)@107..108 " " - Syntax(DocTagAttributeUse)@108..119 - Token(TkLeftBracket)@108..109 "[" - Syntax(DocAttributeUse)@109..118 - Syntax(TypeName)@109..118 - Token(TkName)@109..118 "attribute" - Token(TkRightBracket)@118..119 "]" - Token(TkWhitespace)@119..120 " " - Syntax(TypeName)@120..126 - Token(TkName)@120..126 "number" - Token(TkComma)@126..127 "," - Token(TkWhitespace)@127..128 " " - Syntax(DocTagAttributeUse)@128..139 - Token(TkLeftBracket)@128..129 "[" - Syntax(DocAttributeUse)@129..138 - Syntax(TypeName)@129..138 - Token(TkName)@129..138 "attribute" - Token(TkRightBracket)@138..139 "]" - Token(TkWhitespace)@139..140 " " - Syntax(TypeName)@140..146 - Token(TkName)@140..146 "string" - Token(TkEndOfLine)@146..147 "\n" - Token(TkWhitespace)@147..155 " " - "#; - assert_ast_eq!(code, result); - } } diff --git a/crates/emmylua_parser/src/syntax/node/doc/mod.rs b/crates/emmylua_parser/src/syntax/node/doc/mod.rs index dd4b75ca6..49082e5bc 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/mod.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/mod.rs @@ -242,10 +242,6 @@ impl LuaDocGenericDecl { pub fn is_variadic(&self) -> bool { self.token_by_kind(LuaTokenKind::TkDots).is_some() } - - pub fn get_tag_attribute_use(&self) -> Option { - self.child() - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/emmylua_parser/src/syntax/node/doc/tag.rs b/crates/emmylua_parser/src/syntax/node/doc/tag.rs index dd678f2ad..8c6a9b23c 100644 --- a/crates/emmylua_parser/src/syntax/node/doc/tag.rs +++ b/crates/emmylua_parser/src/syntax/node/doc/tag.rs @@ -488,10 +488,6 @@ impl LuaDocTagParam { pub fn get_type(&self) -> Option { self.child() } - - pub fn get_tag_attribute_use(&self) -> Option { - self.child() - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -534,26 +530,18 @@ impl LuaDocTagReturn { self.children() } - pub fn get_info_list( - &self, - ) -> Vec<( - LuaDocType, - Option, - Option, - )> { + pub fn get_info_list(&self) -> Vec<(LuaDocType, Option)> { let mut result = Vec::new(); let mut current_type = None; let mut current_name = None; - let mut current_attribute = None; for child in self.syntax.children_with_tokens() { match child.kind() { LuaKind::Token(LuaTokenKind::TkComma) => { if let Some(type_) = current_type { - result.push((type_, current_name, current_attribute)); + result.push((type_, current_name)); } current_type = None; current_name = None; - current_attribute = None; } LuaKind::Token(LuaTokenKind::TkName) => { current_name = Some(LuaNameToken::cast(child.into_token().unwrap()).unwrap()); @@ -561,16 +549,12 @@ impl LuaDocTagReturn { k if LuaDocType::can_cast(k.into()) => { current_type = Some(LuaDocType::cast(child.into_node().unwrap()).unwrap()); } - a if LuaDocTagAttributeUse::can_cast(a.into()) => { - current_attribute = - Some(LuaDocTagAttributeUse::cast(child.into_node().unwrap()).unwrap()); - } _ => {} } } if let Some(type_) = current_type { - result.push((type_, current_name, current_attribute)); + result.push((type_, current_name)); } result