Skip to content

Commit e642ebf

Browse files
committed
attribute: add field_accessor
1 parent 7eb8cf4 commit e642ebf

File tree

18 files changed

+336
-150
lines changed

18 files changed

+336
-150
lines changed

Cargo.lock

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

crates/emmylua_code_analysis/resources/std/builtin.lua

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
--- Skip partial diagnostics, typically used to optimize diagnostic performance.
148148
---
149149
--- Receives a parameter, the options are:
150-
--- - `table_field` - Skip diagnostic for `table` fields
150+
--- - `table_field` - Skip diagnostic for `table` fields. Usually attached to configuration tables that do not require actual diagnostic fields.
151151
---@attribute skip_diagnostic(code: string)
152152

153153
--- Index field alias, will be displayed in `hint` and `completion`.
@@ -163,3 +163,12 @@
163163
--- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true`
164164
--- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true`
165165
---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?)
166+
167+
--- Associates `getter` and `setter` methods with a field. Currently provides only definition navigation functionality,
168+
--- and the target methods must reside within the same class.
169+
---
170+
--- params:
171+
--- - convention: Naming convention, defaults to `camelCase`. Implicitly adds `get` and `set` prefixes. eg: `_age` -> `getAge`, `setAge`.
172+
--- - getter: Getter method name. Takes precedence over `convention`.
173+
--- - setter: Setter method name. Takes precedence over `convention`.
174+
---@attribute field_accessor(convention: "camelCase"|"PascalCase"|"snake_case"|nil, getter: string?, setter: string?)

crates/emmylua_code_analysis/src/compilation/analyzer/doc/attribute_tags.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,47 @@ pub fn infer_attribute_uses(
4343
let attribute_uses = tag_use.get_attribute_uses();
4444
let mut result = Vec::new();
4545
for attribute_use in attribute_uses {
46-
let mut params = Vec::new();
47-
if let Some(attribute_call_arg_list) = attribute_use.get_arg_list() {
48-
for arg in attribute_call_arg_list.get_args() {
49-
let arg_type = infer_attribute_arg_type(arg);
50-
params.push(arg_type);
51-
}
52-
}
5346
let attribute_type = infer_type(analyzer, LuaDocType::Name(attribute_use.get_type()?));
5447
if let LuaType::Ref(type_id) = attribute_type {
48+
let arg_types: Vec<LuaType> = attribute_use
49+
.get_arg_list()
50+
.map(|arg_list| arg_list.get_args().map(infer_attribute_arg_type).collect())
51+
.unwrap_or_default();
52+
let param_names = analyzer
53+
.db
54+
.get_type_index()
55+
.get_type_decl(&type_id)
56+
.and_then(|decl| decl.get_attribute_type())
57+
.and_then(|typ| match typ {
58+
LuaType::DocAttribute(attr_type) => Some(
59+
attr_type
60+
.get_params()
61+
.iter()
62+
.map(|(name, _)| name.clone())
63+
.collect::<Vec<_>>(),
64+
),
65+
_ => None,
66+
})
67+
.unwrap_or_default();
68+
69+
let mut params = Vec::new();
70+
for (idx, arg_type) in arg_types.into_iter().enumerate() {
71+
let param_name = param_names
72+
.get(idx)
73+
.cloned()
74+
.or_else(|| {
75+
param_names.last().and_then(|last| {
76+
if last == "..." {
77+
Some(last.clone())
78+
} else {
79+
None
80+
}
81+
})
82+
})
83+
.unwrap_or_default();
84+
params.push((param_name, Some(arg_type)));
85+
}
86+
5587
result.push(LuaAttributeUse::new(type_id, params));
5688
}
5789
}

crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,12 @@ pub fn try_resolve_constructor(
270270
.flatten()
271271
.find(|attr| attr.id.get_name() == "constructor")
272272
.ok_or(InferFailReason::None)?;
273-
let LuaType::DocStringConst(target_signature_name) =
274-
constructor_use.args.first().ok_or(InferFailReason::None)?
275-
else {
273+
let target_signature_type = constructor_use
274+
.args
275+
.first()
276+
.and_then(|(_, typ)| typ.as_ref())
277+
.ok_or(InferFailReason::None)?;
278+
let LuaType::DocStringConst(target_signature_name) = target_signature_type else {
276279
return Err(InferFailReason::None);
277280
};
278281
let target_type_decl_id = get_constructor_target_type(
@@ -288,20 +291,24 @@ pub fn try_resolve_constructor(
288291
let members =
289292
find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?;
290293
let ctor_signature_member = members.first().ok_or(InferFailReason::None)?;
291-
let strip_self = {
292-
if let Some(LuaType::DocBooleanConst(strip_self)) = constructor_use.args.get(1) {
293-
*strip_self
294-
} else {
295-
true
296-
}
297-
};
298-
let return_self = {
299-
if let Some(LuaType::DocBooleanConst(return_self)) = constructor_use.args.get(2) {
300-
*return_self
301-
} else {
302-
true
303-
}
304-
};
294+
let strip_self = constructor_use
295+
.args
296+
.get(1)
297+
.and_then(|(_, typ)| typ.as_ref())
298+
.and_then(|typ| match typ {
299+
LuaType::DocBooleanConst(value) => Some(*value),
300+
_ => None,
301+
})
302+
.unwrap_or(true);
303+
let return_self = constructor_use
304+
.args
305+
.get(2)
306+
.and_then(|(_, typ)| typ.as_ref())
307+
.and_then(|typ| match typ {
308+
LuaType::DocBooleanConst(value) => Some(*value),
309+
_ => None,
310+
})
311+
.unwrap_or(true);
305312
set_signature_to_default_call(db, cache, ctor_signature_member, strip_self, return_self)
306313
.ok_or(InferFailReason::None)?;
307314

Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,6 @@
11
#[cfg(test)]
22
mod test {
3-
use crate::VirtualWorkspace;
4-
5-
#[test]
6-
fn test_def_attribute() {
7-
let mut ws = VirtualWorkspace::new();
8-
ws.def(
9-
r#"
10-
---@attribute Deprecated(message: string?)
11-
---@attribute SkipDiagnosticTable() -- 跳过对表的部分诊断, 用于优化性能, 通常来说对巨型配置表使用.
12-
---@attribute IndexFieldAlias(name: string) -- 索引字段别名, 将在`hint`与`completion`中显示别名.
13-
"#,
14-
);
15-
16-
// ws.def(
17-
// r#"
18-
// ---@attribute check_point(x: string, y: number)
19-
// "#,
20-
// );
21-
22-
// ws.def(
23-
// r#"
24-
// ---@attribute SkipDiagnosticTable()
25-
26-
// ---@[SkipDiagnosticTable, Skip]
27-
// local config = {}
28-
// "#,
29-
// );
30-
31-
ws.def(
32-
r#"
33-
---@class A
34-
---@field a string
35-
---@[Deprecated]
36-
---@field b string
37-
"#,
38-
);
39-
40-
// ws.def(
41-
// r#"
42-
43-
// ---@[deprecated]
44-
// local a
45-
// "#,
46-
// );
47-
}
48-
49-
#[test]
50-
fn test_attribute_attach() {
51-
let mut ws = VirtualWorkspace::new();
52-
// ws.def(
53-
// r#"
54-
// ---@generic [attribute] T
55-
// local function f()
56-
// end
57-
// "#,
58-
// );
59-
ws.def(
60-
r#"
61-
---@generic [attribute] T
62-
local function f()
63-
end
64-
"#,
65-
);
66-
}
3+
use crate::{DiagnosticCode, VirtualWorkspace};
674

685
#[test]
696
fn test_constructor() {
@@ -89,25 +26,18 @@ mod test {
8926
"#,
9027
),
9128
]);
29+
}
9230

93-
// ws.def(
94-
// r#"
95-
// ---@attribute constructor(name: string, strip_self: boolean?, return_self: boolean?)
96-
97-
// ---@generic T
98-
// ---@param [constructor("__init")] name `T`
99-
// ---@return T
100-
// function meta(name)
101-
// end
102-
// "#,
103-
// );
104-
// ws.def(
105-
// r#"
106-
// A = meta("A")
31+
#[test]
32+
fn test_def_attribute() {
33+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
10734

108-
// function A:__init(a)
109-
// end
110-
// "#,
111-
// );
35+
ws.check_code_for(
36+
DiagnosticCode::AssignTypeMismatch,
37+
r#"
38+
---@[skip_diagnostic("table_field")]
39+
local config = {}
40+
"#,
41+
);
11242
}
11343
}

crates/emmylua_code_analysis/src/db_index/property/property.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,18 @@ impl LuaPropertyId {
175175
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
176176
pub struct LuaAttributeUse {
177177
pub id: LuaTypeDeclId,
178-
pub args: Vec<LuaType>,
178+
pub args: Vec<(String, Option<LuaType>)>,
179179
}
180180

181181
impl LuaAttributeUse {
182-
pub fn new(name: LuaTypeDeclId, args: Vec<LuaType>) -> Self {
183-
Self { id: name, args }
182+
pub fn new(id: LuaTypeDeclId, args: Vec<(String, Option<LuaType>)>) -> Self {
183+
Self { id, args }
184+
}
185+
186+
pub fn get_param_by_name(&self, name: &str) -> Option<&LuaType> {
187+
self.args
188+
.iter()
189+
.find(|(n, _)| n == name)
190+
.and_then(|(_, typ)| typ.as_ref())
184191
}
185192
}

crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use rowan::{NodeOrToken, TextRange};
88

99
use crate::{
1010
DiagnosticCode, LuaDeclExtra, LuaDeclId, LuaMemberKey, LuaSemanticDeclId, LuaType,
11-
SemanticDeclLevel, SemanticModel, TypeCheckFailReason, TypeCheckResult, VariadicType,
12-
infer_index_expr,
11+
LuaTypeDeclId, SemanticDeclLevel, SemanticModel, TypeCheckFailReason, TypeCheckResult,
12+
VariadicType, infer_index_expr,
1313
};
1414

1515
use super::{Checker, DiagnosticContext, humanize_lint_type};
@@ -204,6 +204,7 @@ fn check_local_stat(
204204
Some(())
205205
}
206206

207+
/// 检查整个表, 返回`true`表示诊断出异常.
207208
pub fn check_table_expr(
208209
context: &mut DiagnosticContext,
209210
semantic_model: &SemanticModel,
@@ -218,16 +219,16 @@ pub fn check_table_expr(
218219
.get_property_index()
219220
.get_property(&semantic_decl)
220221
{
221-
if let Some(attribute_uses) = property.attribute_uses() {
222-
for attribute_use in attribute_uses.iter() {
223-
if attribute_use.id.get_name() == "skip_diagnostic" {
224-
if let Some(LuaType::DocStringConst(code)) = attribute_use.args.first() {
225-
if code.as_ref() == "table_field" {
226-
return Some(false);
227-
}
228-
};
222+
if let Some(skip_diagnostic) =
223+
property.find_attribute_use(LuaTypeDeclId::new("skip_diagnostic"))
224+
{
225+
if let Some(LuaType::DocStringConst(code)) =
226+
skip_diagnostic.get_param_by_name("code")
227+
{
228+
if code.as_ref() == "table_field" {
229+
return Some(false);
229230
}
230-
}
231+
};
231232
}
232233
}
233234
}

crates/emmylua_code_analysis/src/diagnostic/checker/deprecated.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ fn check_deprecated(
9999
if let Some(attribute_uses) = property.attribute_uses() {
100100
for attribute_use in attribute_uses.iter() {
101101
if attribute_use.id.get_name() == "deprecated" {
102-
let deprecated_message = match attribute_use.args.first() {
103-
Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(),
104-
_ => "deprecated".to_string(),
105-
};
102+
let deprecated_message =
103+
match attribute_use.args.first().and_then(|(_, typ)| typ.as_ref()) {
104+
Some(LuaType::DocStringConst(message)) => message.as_ref().to_string(),
105+
_ => "deprecated".to_string(),
106+
};
106107
context.add_diagnostic(DiagnosticCode::Deprecated, range, deprecated_message, None);
107108
}
108109
}

crates/emmylua_ls/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ serde_yml.workspace = true
3939
itertools.workspace = true
4040
dirs.workspace = true
4141
wax.workspace = true
42+
internment.workspace = true
4243

4344
[dependencies.clap]
4445
workspace = true

crates/emmylua_ls/src/handlers/completion/add_completions/add_member_completion.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,10 @@ pub fn get_index_alias_name(
363363
.find_attribute_use(LuaTypeDeclId::new("index_alias"))?
364364
.args
365365
.first()
366-
.map(|param| match param {
367-
LuaType::DocStringConst(s) => s.as_ref(),
368-
_ => "",
366+
.and_then(|(_, typ)| typ.as_ref())
367+
.and_then(|param| match param {
368+
LuaType::DocStringConst(s) => Some(s.as_ref()),
369+
_ => None,
369370
})?
370371
.to_string();
371372
Some(alias_label)

0 commit comments

Comments
 (0)