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
178
179
180
181
182
183
184
//! vue/no-unused-properties
//!
//! Disallow unused properties (props, data, computed, etc.).
//!
//! ## Examples
//!
//! ### Invalid
//! ```vue
//! <script setup lang="ts">
//! const props = defineProps<{
//! msg: string
//! unused: number // defined but never used
//! }>()
//! </script>
//!
//! <template>
//! <div>{{ msg }}</div>
//! </template>
//! ```
//!
//! ### Valid
//! ```vue
//! <script setup lang="ts">
//! const props = defineProps<{
//! msg: string
//! count: number
//! }>()
//! </script>
//!
//! <template>
//! <div>{{ msg }} - {{ count }}</div>
//! </template>
//! ```
#![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_relief::ast::RootNode;
static META: RuleMeta = RuleMeta {
name: "vue/no-unused-properties",
description: "Disallow unused properties defined in defineProps",
category: RuleCategory::StronglyRecommended,
fixable: false,
default_severity: Severity::Warning,
};
/// Disallow unused properties
pub struct NoUnusedProperties {
/// Pattern for properties to ignore (e.g., starts with '_')
pub ignore_pattern: Option<String>,
/// Check props defined via defineProps
pub check_props: bool,
}
impl Default for NoUnusedProperties {
fn default() -> Self {
Self {
ignore_pattern: None,
check_props: true,
}
}
}
impl NoUnusedProperties {
/// Check if a property name should be ignored
fn should_ignore(&self, name: &str) -> bool {
// Ignore properties starting with underscore
if name.starts_with('_') {
return true;
}
// Check custom ignore pattern
if let Some(ref pattern) = self.ignore_pattern {
if name.starts_with(pattern.as_str()) {
return true;
}
}
false
}
}
impl Rule for NoUnusedProperties {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn run_on_template<'a>(&self, ctx: &mut LintContext<'a>, _root: &RootNode<'a>) {
// Skip if no analysis available
if !ctx.has_analysis() {
return;
}
// Collect unused props first (to avoid borrow conflicts)
let (unused_props, define_props_loc): (Vec<String>, (u32, u32)) = {
let analysis = ctx.analysis().unwrap();
if !self.check_props {
return;
}
let props = analysis.macros.props();
// Get defineProps call location for error reporting
let loc = analysis
.macros
.define_props()
.map(|call| (call.start, call.end))
.unwrap_or((0, 0));
let unused: Vec<String> = props
.iter()
.filter(|prop| {
// Skip ignored properties
if self.should_ignore(prop.name.as_str()) {
return false;
}
let prop_name = prop.name.as_str();
// Check if prop is used in template scope chain
let is_used_in_scope = analysis.scopes.is_used(prop_name);
// Check if prop is accessed via props object in bindings
let has_props_binding = analysis.bindings.contains("props");
let is_prop_destructured = analysis.bindings.get(prop_name).is_some_and(|bt| {
matches!(
bt,
vize_relief::BindingType::Props
| vize_relief::BindingType::PropsAliased
)
});
// If props object exists and is used, we can't easily track individual prop usage
// in script, so we only report if not used in template AND not destructured
let is_used = is_used_in_scope || is_prop_destructured || has_props_binding;
!is_used
})
.map(|prop| prop.name.to_compact_string())
.collect();
(unused, loc)
};
// Report unused props
for prop_name in unused_props {
ctx.report(
crate::diagnostic::LintDiagnostic::warn(
ctx.current_rule,
format!("Prop '{}' is defined but never used", prop_name),
define_props_loc.0,
define_props_loc.1,
)
.with_help("Remove unused prop or use it in your template/script"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::NoUnusedProperties;
use crate::rule::{Rule, RuleCategory};
#[test]
fn test_meta() {
let rule = NoUnusedProperties::default();
assert_eq!(rule.meta().name, "vue/no-unused-properties");
assert_eq!(rule.meta().category, RuleCategory::StronglyRecommended);
}
#[test]
fn test_should_ignore() {
let rule = NoUnusedProperties::default();
assert!(rule.should_ignore("_internal"));
assert!(!rule.should_ignore("count"));
}
}