Skip to content

Commit 4637088

Browse files
committed
diagnostic: add undefined_field
1 parent 63ed7b4 commit 4637088

File tree

7 files changed

+113
-1
lines changed

7 files changed

+113
-1
lines changed

crates/emmylua_code_analysis/locales/lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,8 @@ Cannot use `...` outside a vararg function.:
121121
en: 'Undefined doc param: `%{name}`'
122122
zh_CN: '指向了未定义的参数 `%{name}`'
123123
zh_HK: '指向了未定義的參數 `%{name}`'
124+
'Undefined field: `%{name}`':
125+
en: 'Undefined field: `%{name}`'
126+
zh_CN: '未定义字段 `%{name}`'
127+
zh_HK: '未定義字段 `%{name}`'
128+

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod syntax_error;
1616
mod undefined_doc_param;
1717
mod undefined_global;
1818
mod duplicate_doc_field;
19+
mod undefined_field;
1920
mod unused;
2021

2122
use emmylua_parser::{LuaAstNode, LuaClosureExpr, LuaComment, LuaStat, LuaSyntaxKind};
@@ -48,6 +49,7 @@ pub fn check_file(context: &mut DiagnosticContext, semantic_model: &SemanticMode
4849
check!(unused);
4950
check!(deprecated);
5051
check!(undefined_global);
52+
check!(undefined_field);
5153
check!(access_invisible);
5254
check!(missing_parameter);
5355
check!(redundant_parameter);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use emmylua_parser::{LuaAstNode, LuaIndexExpr};
2+
3+
use crate::{DiagnosticCode, LuaMemberOwner, LuaType, SemanticModel};
4+
5+
use super::DiagnosticContext;
6+
7+
pub const CODES: &[DiagnosticCode] = &[DiagnosticCode::UndefinedField];
8+
9+
pub fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) -> Option<()> {
10+
let root = semantic_model.get_root().clone();
11+
for index_expr in root.descendants::<LuaIndexExpr>() {
12+
check_index_expr(context, semantic_model, index_expr);
13+
}
14+
15+
Some(())
16+
}
17+
18+
fn check_index_expr(
19+
context: &mut DiagnosticContext,
20+
semantic_model: &SemanticModel,
21+
index_expr: LuaIndexExpr,
22+
) -> Option<()> {
23+
let db = context.db;
24+
let prefix_expr_type = semantic_model.infer_expr(index_expr.get_prefix_expr()?)?;
25+
match prefix_expr_type {
26+
LuaType::Ref(id) | LuaType::Def(id) => {
27+
let member_map = db
28+
.get_member_index()
29+
.get_member_map(LuaMemberOwner::Type(id.clone()))?;
30+
let key = index_expr.get_index_key()?;
31+
let member = member_map.get(&key.clone().into());
32+
if member.is_none() {
33+
context.add_diagnostic(
34+
DiagnosticCode::UndefinedField,
35+
key.clone().get_range()?,
36+
t!("Undefined field: `%{name}`", name = key.get_path_part()).to_string(),
37+
None,
38+
);
39+
}
40+
}
41+
_ => {}
42+
}
43+
44+
Some(())
45+
}

crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub fn is_code_default_enable(code: &DiagnosticCode) -> bool {
103103
match code {
104104
DiagnosticCode::InjectFieldFail => false,
105105
DiagnosticCode::DisableGlobalDefine => false,
106-
DiagnosticCode::UndefinedField => false,
106+
// DiagnosticCode::UndefinedField => false,
107107
DiagnosticCode::IterVariableReassign => false,
108108
DiagnosticCode::CodeStyleCheck => false,
109109
// ... handle other variants

crates/emmylua_code_analysis/src/diagnostic/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ mod redundant_return_value_test;
88
mod disable_line_test;
99
mod undefined_doc_param_test;
1010
mod duplicate_doc_field_test;
11+
mod undefined_field_test;
1112
mod return_type_mismatch_test;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#[cfg(test)]
2+
mod test {
3+
use crate::{DiagnosticCode, VirtualWorkspace};
4+
5+
#[test]
6+
fn test() {
7+
let mut ws = VirtualWorkspace::new();
8+
assert!(!ws.check_code_for(
9+
DiagnosticCode::UndefinedField,
10+
r#"
11+
---@class diagnostic.test3
12+
---@field private a number
13+
14+
---@type diagnostic.test3
15+
local test = {}
16+
17+
local b = test.b
18+
"#
19+
));
20+
21+
assert!(!ws.check_code_for(
22+
DiagnosticCode::UndefinedField,
23+
r#"
24+
---@class diagnostic.test3
25+
---@field private a number
26+
local Test3 = {}
27+
28+
local b = Test3.b
29+
"#
30+
));
31+
}
32+
33+
#[test]
34+
fn test_enum() {
35+
let mut ws = VirtualWorkspace::new();
36+
assert!(!ws.check_code_for(
37+
DiagnosticCode::UndefinedField,
38+
r#"
39+
---@enum diagnostic.enum
40+
local Enum = {
41+
A = 1,
42+
}
43+
44+
local enum_b = Enum["B"]
45+
"#
46+
));
47+
}
48+
}

crates/emmylua_parser/src/syntax/node/lua/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111

1212
pub use expr::*;
1313
pub use path_trait::*;
14+
use rowan::TextRange;
1415
pub use stat::*;
1516

1617
use super::{LuaLiteralToken, LuaNameToken, LuaNumberToken, LuaStringToken};
@@ -390,6 +391,16 @@ impl LuaIndexKey {
390391
}
391392
}
392393
}
394+
395+
pub fn get_range(&self) -> Option<TextRange> {
396+
match self {
397+
LuaIndexKey::Name(token) => Some(token.get_range()),
398+
LuaIndexKey::String(token) => Some(token.get_range()),
399+
LuaIndexKey::Integer(token) => Some(token.get_range()),
400+
LuaIndexKey::Expr(expr) => Some(expr.syntax().text_range()),
401+
LuaIndexKey::Idx(_) => None,
402+
}
403+
}
393404
}
394405

395406
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

0 commit comments

Comments
 (0)