Skip to content

Commit 08576b6

Browse files
committed
feat(diagnostic): The deprecation checker now inspects type references.
1 parent e8a53b6 commit 08576b6

4 files changed

Lines changed: 303 additions & 11 deletions

File tree

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

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use crate::{
55

66
use super::{
77
DocAnalyzer,
8-
tags::{find_owner_closure_or_report, get_owner_id_or_report},
8+
tags::{find_owner_closure_or_report, get_owner_id, get_owner_id_or_report},
99
};
1010
use emmylua_parser::{
11-
LuaAst, LuaAstNode, LuaDocDescriptionOwner, LuaDocTagAsync, LuaDocTagDeprecated,
11+
LuaAst, LuaAstNode, LuaDocDescriptionOwner, LuaDocTag, LuaDocTagAsync, LuaDocTagDeprecated,
1212
LuaDocTagNodiscard, LuaDocTagReadonly, LuaDocTagSource, LuaDocTagVersion, LuaDocTagVisibility,
1313
LuaExpr,
1414
};
@@ -105,15 +105,84 @@ pub fn analyze_deprecated(analyzer: &mut DocAnalyzer, tag: LuaDocTagDeprecated)
105105
let message = tag
106106
.get_description()
107107
.map(|desc| desc.get_description_text().to_string());
108+
109+
let mut type_owner_id = None;
110+
if let Some(current_type_id) = &analyzer.current_type_id {
111+
type_owner_id = Some(LuaSemanticDeclId::TypeDecl(current_type_id.clone()));
112+
} else {
113+
let file_id = analyzer.file_id;
114+
let workspace_id = analyzer.workspace_id;
115+
let tags = analyzer.comment.get_doc_tags();
116+
for tag in tags {
117+
match tag {
118+
LuaDocTag::Class(class) => {
119+
if let Some(name_token) = class.get_name_token() {
120+
let name = name_token.get_name_text().to_string();
121+
if let Some(decl) = analyzer.get_db().get_type_index().find_type_decl(
122+
file_id,
123+
&name,
124+
Some(workspace_id),
125+
) {
126+
if decl.is_class() {
127+
type_owner_id = Some(LuaSemanticDeclId::TypeDecl(decl.get_id()));
128+
break;
129+
}
130+
}
131+
}
132+
}
133+
LuaDocTag::Alias(alias) => {
134+
if let Some(name_token) = alias.get_name_token() {
135+
let name = name_token.get_name_text().to_string();
136+
if let Some(decl) = analyzer.get_db().get_type_index().find_type_decl(
137+
file_id,
138+
&name,
139+
Some(workspace_id),
140+
) {
141+
if decl.is_alias() {
142+
type_owner_id = Some(LuaSemanticDeclId::TypeDecl(decl.get_id()));
143+
break;
144+
}
145+
}
146+
}
147+
}
148+
_ => {}
149+
}
150+
}
151+
}
152+
153+
if let Some(type_owner_id) = type_owner_id {
154+
add_deprecated(analyzer, type_owner_id, message.clone())?;
155+
let mut compat_owner_id = None;
156+
if let Some(owner) = get_owner_id(analyzer, None, true) {
157+
match owner {
158+
owner @ (LuaSemanticDeclId::LuaDecl(_) | LuaSemanticDeclId::Member(_)) => {
159+
compat_owner_id = Some(owner);
160+
}
161+
_ => {}
162+
}
163+
}
164+
if let Some(compat_owner_id) = compat_owner_id {
165+
add_deprecated(analyzer, compat_owner_id, message)?;
166+
}
167+
return Some(());
168+
}
169+
108170
let owner_id = get_owner_id_or_report(analyzer, &tag)?;
171+
add_deprecated(analyzer, owner_id, message)?;
172+
173+
Some(())
174+
}
109175

176+
fn add_deprecated(
177+
analyzer: &mut DocAnalyzer,
178+
owner_id: LuaSemanticDeclId,
179+
message: Option<String>,
180+
) -> Option<()> {
110181
analyzer
111182
.type_context
112183
.db
113184
.get_property_index_mut()
114-
.add_deprecated(analyzer.file_id, owner_id, message);
115-
116-
Some(())
185+
.add_deprecated(analyzer.file_id, owner_id, message)
117186
}
118187

119188
pub fn analyze_version(analyzer: &mut DocAnalyzer, version: LuaDocTagVersion) -> Option<()> {

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

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use emmylua_parser::{LuaAst, LuaAstNode, LuaIndexExpr, LuaNameExpr};
1+
use emmylua_parser::{LuaAst, LuaAstNode, LuaDocNameType, LuaIndexExpr, LuaNameExpr};
22

33
use crate::{
44
DiagnosticCode, LuaDeclId, LuaDeprecated, LuaMemberId, LuaSemanticDeclId, SemanticDeclLevel,
@@ -22,6 +22,9 @@ impl Checker for DeprecatedChecker {
2222
LuaAst::LuaIndexExpr(index_expr) => {
2323
check_index_expr(context, semantic_model, index_expr);
2424
}
25+
LuaAst::LuaDocNameType(name_type) => {
26+
check_doc_name_type(context, semantic_model, name_type);
27+
}
2528
_ => {}
2629
}
2730
}
@@ -74,6 +77,32 @@ fn check_index_expr(
7477
Some(())
7578
}
7679

80+
fn check_doc_name_type(
81+
context: &mut DiagnosticContext,
82+
semantic_model: &SemanticModel,
83+
name_type: LuaDocNameType,
84+
) -> Option<()> {
85+
let semantic_decl = semantic_model.find_decl(
86+
rowan::NodeOrToken::Node(name_type.syntax().clone()),
87+
SemanticDeclLevel::default(),
88+
)?;
89+
90+
let LuaSemanticDeclId::TypeDecl(_) = &semantic_decl else {
91+
return Some(());
92+
};
93+
94+
if let Some(deprecated_message) = get_deprecated_message(semantic_model, &semantic_decl) {
95+
context.add_diagnostic(
96+
DiagnosticCode::Deprecated,
97+
name_type.get_range(),
98+
deprecated_message,
99+
None,
100+
);
101+
}
102+
103+
Some(())
104+
}
105+
77106
fn check_deprecated(
78107
context: &mut DiagnosticContext,
79108
semantic_model: &SemanticModel,
@@ -87,14 +116,11 @@ fn check_deprecated(
87116
let Some(property) = property else {
88117
return;
89118
};
90-
if let Some(deprecated) = property.deprecated() {
91-
let deprecated_message = match deprecated {
92-
LuaDeprecated::Deprecated => "deprecated".to_string(),
93-
LuaDeprecated::DeprecatedWithMessage(message) => message.to_string(),
94-
};
95119

120+
if let Some(deprecated_message) = get_deprecated_message(semantic_model, semantic_decl) {
96121
context.add_diagnostic(DiagnosticCode::Deprecated, range, deprecated_message, None);
97122
}
123+
98124
// 检查特性
99125
if let Some(attribute_uses) = property.attribute_uses() {
100126
for attribute_use in attribute_uses.iter() {
@@ -105,3 +131,25 @@ fn check_deprecated(
105131
}
106132
}
107133
}
134+
135+
fn get_deprecated_message(
136+
semantic_model: &SemanticModel,
137+
semantic_decl: &LuaSemanticDeclId,
138+
) -> Option<String> {
139+
let property = semantic_model
140+
.get_db()
141+
.get_property_index()
142+
.get_property(semantic_decl);
143+
let Some(property) = property else {
144+
return None;
145+
};
146+
if let Some(deprecated) = property.deprecated() {
147+
let deprecated_message = match deprecated {
148+
LuaDeprecated::Deprecated => "deprecated".to_string(),
149+
LuaDeprecated::DeprecatedWithMessage(message) => message.to_string(),
150+
};
151+
return Some(deprecated_message);
152+
}
153+
154+
None
155+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#[cfg(test)]
2+
mod test {
3+
use emmylua_parser::{LuaAstNode, LuaLocalName};
4+
5+
use crate::{DiagnosticCode, LuaDeclId, LuaSemanticDeclId, VirtualWorkspace};
6+
7+
fn assert_type_decl_deprecated(content: &str, name: &str) {
8+
let mut ws = VirtualWorkspace::new();
9+
let file_id = ws.def(content);
10+
let db = ws.analysis.compilation.get_db();
11+
let type_decl = db
12+
.get_type_index()
13+
.find_type_decl(file_id, name, db.resolve_workspace_id(file_id))
14+
.expect("type declaration must exist");
15+
let property = db
16+
.get_property_index()
17+
.get_property(&LuaSemanticDeclId::TypeDecl(type_decl.get_id()))
18+
.expect("type declaration property must exist");
19+
20+
assert!(property.deprecated().is_some());
21+
}
22+
23+
fn assert_lua_decl_deprecated(content: &str, name: &str) {
24+
let mut ws = VirtualWorkspace::new();
25+
let file_id = ws.def(content);
26+
let db = ws.analysis.compilation.get_db();
27+
let local_name = ws.get_node::<LuaLocalName>(file_id);
28+
assert_eq!(local_name.get_text(), name);
29+
let decl = db
30+
.get_decl_index()
31+
.get_decl(&LuaDeclId::new(file_id, local_name.get_position()))
32+
.expect("declaration must exist");
33+
let property = db
34+
.get_property_index()
35+
.get_property(&LuaSemanticDeclId::LuaDecl(decl.get_id()))
36+
.expect("declaration property must exist");
37+
38+
assert!(property.deprecated().is_some());
39+
}
40+
41+
#[test]
42+
fn test_deprecated_alias_use() {
43+
let mut ws = VirtualWorkspace::new();
44+
45+
assert!(ws.has_no_diagnostic(
46+
DiagnosticCode::Deprecated,
47+
r#"
48+
---@deprecated test
49+
---@alias std.ConstTpl<T> unknown
50+
"#
51+
));
52+
}
53+
54+
#[test]
55+
fn test_deprecated_alias_no_usage_error() {
56+
let mut ws = VirtualWorkspace::new();
57+
58+
assert!(ws.has_no_diagnostic(
59+
DiagnosticCode::AnnotationUsageError,
60+
r#"
61+
---@deprecated test
62+
---@alias std.ConstTpl<T> unknown
63+
"#
64+
));
65+
}
66+
67+
#[test]
68+
fn test_deprecated_alias_attaches_to_type_decl() {
69+
assert_type_decl_deprecated(
70+
r#"
71+
---@deprecated test
72+
---@alias ConstTpl unknown
73+
"#,
74+
"ConstTpl",
75+
);
76+
}
77+
78+
#[test]
79+
fn test_deprecated_alias_after_alias_attaches_to_type_decl() {
80+
assert_type_decl_deprecated(
81+
r#"
82+
---@alias ConstTpl unknown
83+
---@deprecated test
84+
"#,
85+
"ConstTpl",
86+
);
87+
}
88+
89+
#[test]
90+
fn test_deprecated_class_no_usage_error() {
91+
let mut ws = VirtualWorkspace::new();
92+
93+
assert!(ws.has_no_diagnostic(
94+
DiagnosticCode::AnnotationUsageError,
95+
r#"
96+
---@deprecated test
97+
---@class Foo
98+
"#
99+
));
100+
}
101+
102+
#[test]
103+
fn test_deprecated_class_attaches_to_type_decl() {
104+
assert_type_decl_deprecated(
105+
r#"
106+
---@deprecated test
107+
---@class Foo
108+
local Foo = {}
109+
"#,
110+
"Foo",
111+
);
112+
}
113+
114+
#[test]
115+
fn test_deprecated_class_usage_diagnostic() {
116+
let mut ws = VirtualWorkspace::new();
117+
118+
assert!(!ws.has_no_diagnostic(
119+
DiagnosticCode::Deprecated,
120+
r#"
121+
---@deprecated test
122+
---@class Foo
123+
local Foo = {}
124+
125+
local x = Foo
126+
"#
127+
));
128+
}
129+
130+
#[test]
131+
fn test_deprecated_class_type_annotation_diagnostic() {
132+
let mut ws = VirtualWorkspace::new();
133+
134+
assert!(!ws.has_no_diagnostic(
135+
DiagnosticCode::Deprecated,
136+
r#"
137+
---@deprecated
138+
---@class A
139+
140+
---@type A
141+
local a
142+
"#
143+
));
144+
}
145+
146+
#[test]
147+
fn test_deprecated_class_param_annotation_diagnostic() {
148+
let mut ws = VirtualWorkspace::new();
149+
150+
assert!(!ws.has_no_diagnostic(
151+
DiagnosticCode::Deprecated,
152+
r#"
153+
---@deprecated
154+
---@class A
155+
156+
---@param a A
157+
local function f(a)
158+
end
159+
"#
160+
));
161+
}
162+
163+
#[test]
164+
fn test_deprecated_class_after_class_attaches_to_decl() {
165+
assert_lua_decl_deprecated(
166+
r#"
167+
---@class Foo
168+
---@deprecated test
169+
local Foo = {}
170+
"#,
171+
"Foo",
172+
);
173+
}
174+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod call_non_callable_test;
55
mod cast_type_mismatch_test;
66
mod check_return_count_test;
77
mod code_style;
8+
mod deprecated_test;
89
mod disable_line_test;
910
mod duplicate_field_test;
1011
mod duplicate_index_test;

0 commit comments

Comments
 (0)