1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use emmylua_parser::LuaChunk;
use rowan::TextRange;
use crate::{DiagnosticCode, LuaDecl, LuaReferenceIndex, SemanticModel};
use super::{Checker, DiagnosticContext};
pub struct UnusedChecker;
impl Checker for UnusedChecker {
const CODES: &[DiagnosticCode] = &[DiagnosticCode::Unused];
fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) {
let file_id = semantic_model.get_file_id();
let Some(decl_tree) = semantic_model
.get_db()
.get_decl_index()
.get_decl_tree(&file_id)
else {
return;
};
let root = semantic_model.get_root();
let ref_index = semantic_model.get_db().get_reference_index();
for (_, decl) in decl_tree.get_decls().iter() {
if decl.is_global() || decl.is_param() && decl.get_name() == "..." {
continue;
}
if let Err(result) = get_unused_check_result(ref_index, decl, root) {
let name = decl.get_name();
if name.starts_with('_') {
continue;
}
match result {
UnusedCheckResult::Unused(range) => {
context.add_diagnostic(
DiagnosticCode::Unused,
range,
t!(
"%{name} is never used, if this is intentional, prefix it with an underscore: _%{name}",
name = name
).to_string(),
None)
}
// UnusedCheckResult::AssignedButNotRead(range) => {
// context.add_diagnostic(
// DiagnosticCode::Unused,
// range,
// t!(
// "Variable '%{name}' is assigned a value but this value is never read, use _%{name} to indicate this is intentional",
// name = name
// ).to_string(),
// None)
// }
UnusedCheckResult::UnusedSelf(range) => {
context.add_diagnostic(
DiagnosticCode::Unused,
range,
t!(
"Implicit self is never used, if this is intentional, please use '.' instead of ':' to define the method",
).to_string(),
None,
);
}
}
}
}
}
}
enum UnusedCheckResult {
Unused(TextRange),
// AssignedButNotRead(TextRange),
UnusedSelf(TextRange),
}
fn get_unused_check_result(
ref_index: &LuaReferenceIndex,
decl: &LuaDecl,
_root: &LuaChunk,
) -> Result<(), UnusedCheckResult> {
let decl_range = decl.get_range();
let file_id = decl.get_file_id();
let decl_ref = match ref_index.get_decl_references(&file_id, &decl.get_id()) {
Some(decl_ref) => decl_ref,
None => {
if decl.is_implicit_self() {
return Err(UnusedCheckResult::UnusedSelf(decl_range));
}
return Err(UnusedCheckResult::Unused(decl_range));
}
};
if decl_ref.cells.is_empty() {
return Err(UnusedCheckResult::Unused(decl_range));
}
// if decl_ref.mutable {
// let last_ref_cell = decl_ref
// .cells
// .last()
// .ok_or(UnusedCheckResult::Unused(decl_range))?;
// if last_ref_cell.is_write
// && let Some(result) =
// check_last_mutable_is_read(decl_range.start(), decl_ref, last_ref_cell.range, root)
// {
// return Err(result);
// }
// }
Ok(())
}
// remove for future implement
// fn check_last_mutable_is_read(
// decl_position: TextSize,
// decl_ref: &DeclReference,
// range: TextRange,
// root: &LuaChunk,
// ) -> Option<UnusedCheckResult> {
// let syntax_id = LuaSyntaxId::new(LuaSyntaxKind::NameExpr.into(), range);
// let node = LuaNameExpr::cast(syntax_id.to_node_from_root(root.syntax())?)?;
// for ancestor_node in node.ancestors::<LuaAst>() {
// // decl's parent
// if ancestor_node.syntax().text_range().contains(decl_position) {
// return Some(UnusedCheckResult::AssignedButNotRead(range));
// }
// if let Some(loop_stat) = LuaLoopStat::cast(ancestor_node.syntax().clone()) {
// // in a loop stat
// let loop_range = loop_stat.syntax().text_range();
// for ref_cell in decl_ref.cells.iter() {
// if !ref_cell.is_write && loop_range.contains(ref_cell.range.start()) {
// return None;
// }
// }
// } else if ancestor_node.syntax().kind() == LuaSyntaxKind::ClosureExpr.into() {
// return None;
// }
// }
// // not in a loop stat
// Some(UnusedCheckResult::AssignedButNotRead(range))
// }