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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! vue/no-unused-vars
//!
//! Disallow unused variable definitions in `v-for` and `v-slot` directives.
//!
//! This rule reports variables that are defined in `v-for` or `v-slot` directives
//! but never used in the template. Uses croquis semantic analysis for accurate
//! tracking of variable usage.
//!
//! ## Examples
//!
//! ### Invalid
//! ```vue
//! <!-- 'index' is defined but never used -->
//! <li v-for="(item, index) in items" :key="item.id">{{ item }}</li>
//!
//! <!-- 'foo' is defined but never used -->
//! <template v-slot="{ foo }">
//! <span>Hello</span>
//! </template>
//! ```
//!
//! ### Valid
//! ```vue
//! <li v-for="(item, index) in items" :key="index">{{ item }}</li>
//! <li v-for="item in items" :key="item.id">{{ item.name }}</li>
//!
//! <!-- Underscore prefix indicates intentionally unused -->
//! <li v-for="(item, _index) in items" :key="item.id">{{ item }}</li>
//!
//! <template v-slot="{ data }">
//! <span>{{ data }}</span>
//! </template>
//! ```
//!
//! ## Options
//!
//! Variables starting with `_` are ignored by default (e.g., `_unused`).
#![allow(clippy::disallowed_macros)]
use crate::context::LintContext;
use crate::diagnostic::Severity;
use crate::rule::{Rule, RuleCategory, RuleMeta};
use vize_carton::String;
use vize_carton::ToCompactString;
use vize_croquis::UnusedVarContext;
use vize_relief::ast::RootNode;
static META: RuleMeta = RuleMeta {
name: "vue/no-unused-vars",
description: "Disallow unused variable definitions in v-for and v-slot directives",
category: RuleCategory::Essential,
fixable: false,
default_severity: Severity::Warning,
};
/// Disallow unused v-for and v-slot variables
pub struct NoUnusedVars {
/// Pattern for variables to ignore (default: starts with '_')
ignore_pattern: Option<String>,
}
impl Default for NoUnusedVars {
fn default() -> Self {
Self {
ignore_pattern: Some("^_".to_compact_string()),
}
}
}
impl NoUnusedVars {
/// Check if a variable name should be ignored
fn should_ignore(&self, name: &str) -> bool {
// By default, ignore variables starting with underscore
if name.starts_with('_') {
return true;
}
// Check custom ignore pattern
if let Some(ref pattern) = &self.ignore_pattern {
if pattern == "^_" {
return name.starts_with('_');
}
if name.starts_with(pattern.as_str()) {
return true;
}
}
false
}
}
impl Rule for NoUnusedVars {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn run_on_template<'a>(&self, ctx: &mut LintContext<'a>, _root: &RootNode<'a>) {
// Skip if no analysis available (croquis semantic analysis required)
if !ctx.has_analysis() {
return;
}
let analysis = ctx.analysis().unwrap();
let unused_vars = analysis.unused_template_vars();
for unused in unused_vars {
// Skip if the variable should be ignored
if self.should_ignore(unused.name.as_str()) {
continue;
}
let (message, help) = match &unused.context {
UnusedVarContext::VForValue => (
format!(
"Variable '{}' is defined by v-for but never used",
unused.name
),
"If the variable is intentionally unused, prefix it with underscore: _item",
),
UnusedVarContext::VForKey => (
format!(
"Key variable '{}' is defined by v-for but never used",
unused.name
),
"Consider removing the key variable or prefix it with underscore: _key",
),
UnusedVarContext::VForIndex => (
format!(
"Index variable '{}' is defined by v-for but never used",
unused.name
),
"Consider removing the index variable or prefix it with underscore: _index",
),
UnusedVarContext::VSlot { slot_name } => (
format!(
"Slot prop '{}' from slot '{}' is defined but never used",
unused.name, slot_name
),
"Consider removing unused slot props or prefix with underscore",
),
};
ctx.report(
crate::diagnostic::LintDiagnostic::warn(
ctx.current_rule,
&message,
unused.offset,
unused.offset + unused.name.len() as u32,
)
.with_help(help),
);
}
}
}
#[cfg(test)]
mod tests {
use super::NoUnusedVars;
use crate::rule::{Rule, RuleCategory};
#[test]
fn test_meta() {
let rule = NoUnusedVars::default();
assert_eq!(rule.meta().name, "vue/no-unused-vars");
assert_eq!(rule.meta().category, RuleCategory::Essential);
}
#[test]
fn test_should_ignore_underscore_prefix() {
let rule = NoUnusedVars::default();
assert!(rule.should_ignore("_item"));
assert!(rule.should_ignore("_"));
assert!(!rule.should_ignore("item"));
assert!(!rule.should_ignore("index"));
}
}