Skip to content

Commit fcd1d1e

Browse files
committed
将 export 检查从 check field 中分离
1 parent 26377c8 commit fcd1d1e

File tree

4 files changed

+262
-94
lines changed

4 files changed

+262
-94
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use std::collections::HashSet;
2+
3+
use emmylua_parser::{LuaAst, LuaAstNode, LuaCallExpr, LuaIndexExpr, LuaVarExpr};
4+
5+
use crate::{
6+
DiagnosticCode, LuaSemanticDeclId, LuaType, ModuleInfo, SemanticDeclLevel, SemanticModel,
7+
parse_require_module_info,
8+
};
9+
10+
use super::{Checker, DiagnosticContext, check_field, humanize_lint_type};
11+
12+
pub struct CheckExportChecker;
13+
14+
impl Checker for CheckExportChecker {
15+
const CODES: &[DiagnosticCode] = &[DiagnosticCode::InjectField, DiagnosticCode::UndefinedField];
16+
17+
fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) {
18+
let root = semantic_model.get_root().clone();
19+
let mut checked_index_expr = HashSet::new();
20+
for node in root.descendants::<LuaAst>() {
21+
match node {
22+
LuaAst::LuaAssignStat(assign) => {
23+
let (vars, _) = assign.get_var_and_expr_list();
24+
for var in vars.iter() {
25+
if let LuaVarExpr::IndexExpr(index_expr) = var {
26+
checked_index_expr.insert(index_expr.syntax().clone());
27+
check_export_index_expr(
28+
context,
29+
semantic_model,
30+
index_expr,
31+
DiagnosticCode::InjectField,
32+
);
33+
}
34+
}
35+
}
36+
LuaAst::LuaIndexExpr(index_expr) => {
37+
if checked_index_expr.contains(index_expr.syntax()) {
38+
continue;
39+
}
40+
check_export_index_expr(
41+
context,
42+
semantic_model,
43+
&index_expr,
44+
DiagnosticCode::UndefinedField,
45+
);
46+
}
47+
_ => {}
48+
}
49+
}
50+
}
51+
}
52+
53+
fn check_export_index_expr(
54+
context: &mut DiagnosticContext,
55+
semantic_model: &SemanticModel,
56+
index_expr: &LuaIndexExpr,
57+
code: DiagnosticCode,
58+
) -> Option<()> {
59+
let db = context.db;
60+
let prefix_expr = index_expr.get_prefix_expr()?;
61+
let prefix_info = semantic_model.get_semantic_info(prefix_expr.syntax().clone().into())?;
62+
let prefix_typ = prefix_info.typ.clone();
63+
64+
// `check_export` 仅需要处理 `TableConst, 其它类型由 `check_field` 负责.
65+
let LuaType::TableConst(table_const) = &prefix_typ else {
66+
return Some(());
67+
};
68+
69+
let index_key = index_expr.get_index_key()?;
70+
71+
// 检查该表是否为导入的表.
72+
if let Some(module_info) = check_require_table_const_with_export(semantic_model, index_expr) {
73+
if code == DiagnosticCode::InjectField {
74+
// 检查字段定义是否来自导入的表.
75+
if let Some(info) = semantic_model.get_semantic_info(index_expr.syntax().clone().into())
76+
&& is_cross_file_member_from_imported_export_table_const(
77+
module_info,
78+
info.semantic_decl,
79+
)
80+
{
81+
let index_name = index_key.get_path_part();
82+
context.add_diagnostic(
83+
DiagnosticCode::InjectField,
84+
index_key.get_range()?,
85+
t!(
86+
"Fields cannot be injected into the reference of `%{class}` for `%{field}`. ",
87+
class = humanize_lint_type(db, &prefix_typ),
88+
field = index_name,
89+
)
90+
.to_string(),
91+
None,
92+
);
93+
return Some(());
94+
}
95+
}
96+
97+
if check_field::is_valid_member(semantic_model, &prefix_typ, index_expr, &index_key, code)
98+
.is_some()
99+
{
100+
return Some(());
101+
}
102+
103+
let index_name = index_key.get_path_part();
104+
match code {
105+
DiagnosticCode::InjectField => {
106+
context.add_diagnostic(
107+
DiagnosticCode::InjectField,
108+
index_key.get_range()?,
109+
t!(
110+
"Fields cannot be injected into the reference of `%{class}` for `%{field}`. ",
111+
class = humanize_lint_type(db, &prefix_typ),
112+
field = index_name,
113+
)
114+
.to_string(),
115+
None,
116+
);
117+
}
118+
DiagnosticCode::UndefinedField => {
119+
context.add_diagnostic(
120+
DiagnosticCode::UndefinedField,
121+
index_key.get_range()?,
122+
t!("Undefined field `%{field}`. ", field = index_name,).to_string(),
123+
None,
124+
);
125+
}
126+
_ => {}
127+
}
128+
129+
return Some(());
130+
}
131+
132+
// 不是导入表, 且定义位于当前文件中, 则尝试检查本地表.
133+
if code != DiagnosticCode::UndefinedField && table_const.file_id != semantic_model.get_file_id()
134+
{
135+
return Some(());
136+
}
137+
138+
let Some(LuaSemanticDeclId::LuaDecl(decl_id)) = prefix_info.semantic_decl else {
139+
return Some(());
140+
};
141+
// 必须为 local 声明
142+
let decl = semantic_model
143+
.get_db()
144+
.get_decl_index()
145+
.get_decl(&decl_id)?;
146+
if !decl.is_local() {
147+
return Some(());
148+
}
149+
// 且该声明标记了 `export`
150+
let property = semantic_model
151+
.get_db()
152+
.get_property_index()
153+
.get_property(&decl_id.into())?;
154+
if property.export().is_none() {
155+
return Some(());
156+
}
157+
158+
if check_field::is_valid_member(semantic_model, &prefix_typ, index_expr, &index_key, code)
159+
.is_some()
160+
{
161+
return Some(());
162+
}
163+
164+
let index_name = index_key.get_path_part();
165+
context.add_diagnostic(
166+
DiagnosticCode::UndefinedField,
167+
index_key.get_range()?,
168+
t!("Undefined field `%{field}`. ", field = index_name,).to_string(),
169+
None,
170+
);
171+
172+
Some(())
173+
}
174+
175+
fn check_require_table_const_with_export<'a>(
176+
semantic_model: &'a SemanticModel,
177+
index_expr: &LuaIndexExpr,
178+
) -> Option<&'a ModuleInfo> {
179+
// 获取前缀表达式的语义信息
180+
let prefix_expr = index_expr.get_prefix_expr()?;
181+
if let Some(call_expr) = LuaCallExpr::cast(prefix_expr.syntax().clone()) {
182+
let module_info = parse_require_expr_module_info(semantic_model, &call_expr)?;
183+
if module_info.is_export(semantic_model.get_db()) {
184+
return Some(module_info);
185+
}
186+
}
187+
188+
let semantic_decl_id = semantic_model.find_decl(
189+
prefix_expr.syntax().clone().into(),
190+
SemanticDeclLevel::NoTrace,
191+
)?;
192+
// 检查是否是声明引用
193+
let decl_id = match semantic_decl_id {
194+
LuaSemanticDeclId::LuaDecl(decl_id) => decl_id,
195+
_ => return None,
196+
};
197+
198+
// 获取声明
199+
let decl = semantic_model
200+
.get_db()
201+
.get_decl_index()
202+
.get_decl(&decl_id)?;
203+
204+
let module_info = parse_require_module_info(semantic_model, &decl)?;
205+
if module_info.is_export(semantic_model.get_db()) {
206+
return Some(module_info);
207+
}
208+
None
209+
}
210+
211+
fn parse_require_expr_module_info<'a>(
212+
semantic_model: &'a SemanticModel,
213+
call_expr: &LuaCallExpr,
214+
) -> Option<&'a ModuleInfo> {
215+
let arg_list = call_expr.get_args_list()?;
216+
let first_arg = arg_list.get_args().next()?;
217+
let require_path_type = semantic_model.infer_expr(first_arg.clone()).ok()?;
218+
let module_path: String = match &require_path_type {
219+
LuaType::StringConst(module_path) => module_path.as_ref().to_string(),
220+
_ => return None,
221+
};
222+
223+
semantic_model
224+
.get_db()
225+
.get_module_index()
226+
.find_module(&module_path)
227+
}
228+
229+
fn is_cross_file_member_from_imported_export_table_const(
230+
module_info: &ModuleInfo,
231+
semantic_decl: Option<LuaSemanticDeclId>,
232+
) -> bool {
233+
if let Some(LuaSemanticDeclId::Member(member_id)) = semantic_decl
234+
&& module_info.file_id != member_id.file_id
235+
{
236+
return true;
237+
}
238+
239+
false
240+
}

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

Lines changed: 6 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
use std::collections::HashSet;
22

33
use emmylua_parser::{
4-
LuaAst, LuaAstNode, LuaCallExpr, LuaElseIfClauseStat, LuaForRangeStat, LuaForStat, LuaIfStat,
5-
LuaIndexExpr, LuaIndexKey, LuaRepeatStat, LuaSyntaxKind, LuaTokenKind, LuaVarExpr,
6-
LuaWhileStat,
4+
LuaAst, LuaAstNode, LuaElseIfClauseStat, LuaForRangeStat, LuaForStat, LuaIfStat, LuaIndexExpr,
5+
LuaIndexKey, LuaRepeatStat, LuaSyntaxKind, LuaTokenKind, LuaVarExpr, LuaWhileStat,
76
};
87

98
use crate::{
109
DbIndex, DiagnosticCode, InferFailReason, LuaAliasCallKind, LuaAliasCallType, LuaMemberKey,
11-
LuaSemanticDeclId, LuaType, ModuleInfo, SemanticDeclLevel, SemanticModel,
12-
enum_variable_is_param, get_keyof_members, parse_require_module_info,
10+
LuaType, SemanticModel, enum_variable_is_param, get_keyof_members,
1311
};
1412

1513
use super::{Checker, DiagnosticContext, humanize_lint_type};
@@ -65,32 +63,14 @@ fn check_index_expr(
6563
let prefix_typ = semantic_model
6664
.infer_expr(index_expr.get_prefix_expr()?)
6765
.unwrap_or(LuaType::Unknown);
68-
let mut module_info = None;
6966

7067
if is_invalid_prefix_type(&prefix_typ) {
71-
if matches!(prefix_typ, LuaType::TableConst(_)) {
72-
// 如果导入了被 @export 标记的表常量, 那么不应该跳过检查
73-
module_info = check_require_table_const_with_export(semantic_model, index_expr);
74-
if module_info.is_none() {
75-
return Some(());
76-
}
77-
} else {
78-
return Some(());
79-
}
68+
return Some(());
8069
}
8170

8271
let index_key = index_expr.get_index_key()?;
8372

84-
if is_valid_member(
85-
semantic_model,
86-
&prefix_typ,
87-
index_expr,
88-
&index_key,
89-
code,
90-
module_info,
91-
)
92-
.is_some()
93-
{
73+
if is_valid_member(semantic_model, &prefix_typ, index_expr, &index_key, code).is_some() {
9474
return Some(());
9575
}
9676

@@ -141,13 +121,12 @@ fn is_invalid_prefix_type(typ: &LuaType) -> bool {
141121
}
142122
}
143123

144-
fn is_valid_member(
124+
pub(super) fn is_valid_member(
145125
semantic_model: &SemanticModel,
146126
prefix_typ: &LuaType,
147127
index_expr: &LuaIndexExpr,
148128
index_key: &LuaIndexKey,
149129
code: DiagnosticCode,
150-
module_info: Option<&ModuleInfo>,
151130
) -> Option<()> {
152131
match prefix_typ {
153132
LuaType::Global | LuaType::Userdata => return Some(()),
@@ -201,16 +180,6 @@ fn is_valid_member(
201180
};
202181
}
203182

204-
// TODO: 元组类型的检查或许需要独立出来
205-
if !need && code == DiagnosticCode::InjectField {
206-
// 前缀是导入的表常量, 检查定义的文件是否与导入的表常量相同, 不同则认为是非法的
207-
if let Some(module_info) = module_info
208-
&& let Some(LuaSemanticDeclId::Member(member_id)) = info.semantic_decl
209-
&& module_info.file_id != member_id.file_id
210-
{
211-
return None;
212-
}
213-
}
214183
need
215184
}
216185
None => true,
@@ -490,63 +459,6 @@ fn check_enum_is_param(
490459
)
491460
}
492461

493-
/// 检查导入的表常量
494-
fn check_require_table_const_with_export<'a>(
495-
semantic_model: &'a SemanticModel,
496-
index_expr: &LuaIndexExpr,
497-
) -> Option<&'a ModuleInfo> {
498-
// 获取前缀表达式的语义信息
499-
let prefix_expr = index_expr.get_prefix_expr()?;
500-
if let Some(call_expr) = LuaCallExpr::cast(prefix_expr.syntax().clone()) {
501-
let module_info = parse_require_expr_module_info(semantic_model, &call_expr)?;
502-
if module_info.is_export(semantic_model.get_db()) {
503-
return Some(module_info);
504-
}
505-
}
506-
507-
let semantic_decl_id = semantic_model.find_decl(
508-
prefix_expr.syntax().clone().into(),
509-
SemanticDeclLevel::NoTrace,
510-
)?;
511-
// 检查是否是声明引用
512-
let decl_id = match semantic_decl_id {
513-
LuaSemanticDeclId::LuaDecl(decl_id) => decl_id,
514-
_ => return None,
515-
};
516-
517-
// 获取声明
518-
let decl = semantic_model
519-
.get_db()
520-
.get_decl_index()
521-
.get_decl(&decl_id)?;
522-
523-
let module_info = parse_require_module_info(semantic_model, &decl)?;
524-
if module_info.is_export(semantic_model.get_db()) {
525-
return Some(module_info);
526-
}
527-
None
528-
}
529-
530-
pub fn parse_require_expr_module_info<'a>(
531-
semantic_model: &'a SemanticModel,
532-
call_expr: &LuaCallExpr,
533-
) -> Option<&'a ModuleInfo> {
534-
let arg_list = call_expr.get_args_list()?;
535-
let first_arg = arg_list.get_args().next()?;
536-
let require_path_type = semantic_model.infer_expr(first_arg.clone()).ok()?;
537-
let module_path: String = match &require_path_type {
538-
LuaType::StringConst(module_path) => module_path.as_ref().to_string(),
539-
_ => {
540-
return None;
541-
}
542-
};
543-
544-
semantic_model
545-
.get_db()
546-
.get_module_index()
547-
.find_module(&module_path)
548-
}
549-
550462
fn get_keyof_keys(db: &DbIndex, alias_call: &LuaAliasCallType) -> Option<Vec<LuaType>> {
551463
if alias_call.get_call_kind() != LuaAliasCallKind::KeyOf {
552464
return None;

0 commit comments

Comments
 (0)