|
1 | 1 | #[allow(clippy::wildcard_imports)] |
2 | 2 | use oxc_ast::ast::*; |
| 3 | +use oxc_semantic::{Semantic, SymbolId}; |
3 | 4 | use oxc_span::{GetSpan, Span}; |
4 | 5 | use std::ops::BitOr; |
5 | 6 |
|
@@ -45,6 +46,10 @@ pub(super) trait CheckBinding<'a> { |
45 | 46 | ) -> Option<UnusedBindingResult>; |
46 | 47 | } |
47 | 48 |
|
| 49 | +// ============================================================================= |
| 50 | +// ================================= BINDINGS ================================== |
| 51 | +// ============================================================================= |
| 52 | + |
48 | 53 | impl<'a> CheckBinding<'a> for BindingPattern<'a> { |
49 | 54 | fn check_unused_binding_pattern( |
50 | 55 | &self, |
@@ -132,6 +137,113 @@ impl<'a> CheckBinding<'a> for BindingRestElement<'a> { |
132 | 137 | } |
133 | 138 |
|
134 | 139 | impl<'a> CheckBinding<'a> for ArrayPattern<'a> { |
| 140 | + fn check_unused_binding_pattern( |
| 141 | + &self, |
| 142 | + options: &NoUnusedVarsOptions, |
| 143 | + symbol: &Symbol<'_, 'a>, |
| 144 | + ) -> Option<UnusedBindingResult> { |
| 145 | + for el in &self.elements { |
| 146 | + let Some(el) = el.as_ref() else { |
| 147 | + continue; |
| 148 | + }; |
| 149 | + // const [a, _b, c] = arr; console.log(a, b) |
| 150 | + // here, res will contain data for _b, and we want to check if it |
| 151 | + // can be ignored (if it matches destructuredArrayIgnorePattern) |
| 152 | + if let Some(res) = el.check_unused_binding_pattern(options, symbol) { |
| 153 | + // const [{ _a }] = arr shouldn't get ignored since _a is inside |
| 154 | + // an object destructure |
| 155 | + if el.kind.is_destructuring_pattern() { |
| 156 | + return Some(res); |
| 157 | + } |
| 158 | + let is_ignorable = options |
| 159 | + .destructured_array_ignore_pattern |
| 160 | + .as_ref() |
| 161 | + .is_some_and(|pattern| pattern.is_match(symbol.name())); |
| 162 | + return Some(res | is_ignorable); |
| 163 | + } |
| 164 | + } |
| 165 | + None |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +// ============================================================================= |
| 170 | +// ============================== RE-ASSIGNMENTS =============================== |
| 171 | +// ============================================================================= |
| 172 | + |
| 173 | +impl<'a> CheckBinding<'a> for AssignmentExpression<'a> { |
| 174 | + fn check_unused_binding_pattern( |
| 175 | + &self, |
| 176 | + options: &NoUnusedVarsOptions, |
| 177 | + symbol: &Symbol<'_, 'a>, |
| 178 | + ) -> Option<UnusedBindingResult> { |
| 179 | + self.left.check_unused_binding_pattern(options, symbol) |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +impl<'a> CheckBinding<'a> for AssignmentTarget<'a> { |
| 184 | + fn check_unused_binding_pattern( |
| 185 | + &self, |
| 186 | + options: &NoUnusedVarsOptions, |
| 187 | + symbol: &Symbol<'_, 'a>, |
| 188 | + ) -> Option<UnusedBindingResult> { |
| 189 | + match self { |
| 190 | + AssignmentTarget::AssignmentTargetIdentifier(id) => { |
| 191 | + id.check_unused_binding_pattern(options, symbol) |
| 192 | + } |
| 193 | + AssignmentTarget::ArrayAssignmentTarget(arr) => { |
| 194 | + arr.check_unused_binding_pattern(options, symbol) |
| 195 | + } |
| 196 | + AssignmentTarget::ObjectAssignmentTarget(obj) => { |
| 197 | + obj.check_unused_binding_pattern(options, symbol) |
| 198 | + } |
| 199 | + _ => None, |
| 200 | + } |
| 201 | + } |
| 202 | +} |
| 203 | + |
| 204 | +impl<'a> CheckBinding<'a> for ObjectAssignmentTarget<'a> { |
| 205 | + fn check_unused_binding_pattern( |
| 206 | + &self, |
| 207 | + options: &NoUnusedVarsOptions, |
| 208 | + symbol: &Symbol<'_, 'a>, |
| 209 | + ) -> Option<UnusedBindingResult> { |
| 210 | + if options.ignore_rest_siblings && self.rest.is_some() { |
| 211 | + return Some(UnusedBindingResult::from(self.span()).ignore()); |
| 212 | + } |
| 213 | + for el in &self.properties { |
| 214 | + if let Some(res) = el.check_unused_binding_pattern(options, symbol) { |
| 215 | + // has a rest sibling and the rule is configured to |
| 216 | + // ignore variables that have them |
| 217 | + let is_ignorable = options.ignore_rest_siblings && self.rest.is_some(); |
| 218 | + return Some(res | is_ignorable); |
| 219 | + } |
| 220 | + } |
| 221 | + return self |
| 222 | + .rest |
| 223 | + .as_ref() |
| 224 | + .and_then(|rest| rest.target.check_unused_binding_pattern(options, symbol)); |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +impl<'a> CheckBinding<'a> for AssignmentTargetMaybeDefault<'a> { |
| 229 | + fn check_unused_binding_pattern( |
| 230 | + &self, |
| 231 | + options: &NoUnusedVarsOptions, |
| 232 | + symbol: &Symbol<'_, 'a>, |
| 233 | + ) -> Option<UnusedBindingResult> { |
| 234 | + match self { |
| 235 | + Self::AssignmentTargetWithDefault(target) => { |
| 236 | + target.binding.check_unused_binding_pattern(options, symbol) |
| 237 | + } |
| 238 | + target @ match_assignment_target!(Self) => { |
| 239 | + let target = target.as_assignment_target().expect("match_assignment_target matched a node that couldn't be converted into an AssignmentTarget"); |
| 240 | + target.check_unused_binding_pattern(options, symbol) |
| 241 | + } |
| 242 | + } |
| 243 | + } |
| 244 | +} |
| 245 | + |
| 246 | +impl<'a> CheckBinding<'a> for ArrayAssignmentTarget<'a> { |
135 | 247 | fn check_unused_binding_pattern( |
136 | 248 | &self, |
137 | 249 | options: &NoUnusedVarsOptions, |
@@ -159,3 +271,92 @@ impl<'a> CheckBinding<'a> for ArrayPattern<'a> { |
159 | 271 | None |
160 | 272 | } |
161 | 273 | } |
| 274 | +impl<'a> CheckBinding<'a> for AssignmentTargetProperty<'a> { |
| 275 | + fn check_unused_binding_pattern( |
| 276 | + &self, |
| 277 | + options: &NoUnusedVarsOptions, |
| 278 | + symbol: &Symbol<'_, 'a>, |
| 279 | + ) -> Option<UnusedBindingResult> { |
| 280 | + // self.binding.check_unused_binding_pattern(options, symbol) |
| 281 | + match self { |
| 282 | + Self::AssignmentTargetPropertyIdentifier(id) => { |
| 283 | + id.binding.check_unused_binding_pattern(options, symbol) |
| 284 | + } |
| 285 | + Self::AssignmentTargetPropertyProperty(prop) => { |
| 286 | + prop.binding.check_unused_binding_pattern(options, symbol) |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +impl<'a> CheckBinding<'a> for IdentifierReference<'a> { |
| 293 | + fn check_unused_binding_pattern( |
| 294 | + &self, |
| 295 | + _options: &NoUnusedVarsOptions, |
| 296 | + symbol: &Symbol<'_, 'a>, |
| 297 | + ) -> Option<UnusedBindingResult> { |
| 298 | + (symbol == self).then(|| UnusedBindingResult::from(self.span())) |
| 299 | + } |
| 300 | +} |
| 301 | + |
| 302 | +#[derive(Clone, Copy)] |
| 303 | +pub(super) struct BindingContext<'s, 'a> { |
| 304 | + pub options: &'s NoUnusedVarsOptions, |
| 305 | + pub semantic: &'s Semantic<'a>, |
| 306 | + // pub symbol: &'s Symbol<'s, 'a>, |
| 307 | +} |
| 308 | +impl<'s, 'a> BindingContext<'s, 'a> { |
| 309 | + #[inline] |
| 310 | + pub fn symbol(&self, symbol_id: SymbolId) -> Symbol<'s, 'a> { |
| 311 | + Symbol::new(self.semantic, symbol_id) |
| 312 | + } |
| 313 | + #[inline] |
| 314 | + pub fn has_usages(&self, symbol_id: SymbolId) -> bool { |
| 315 | + self.symbol(symbol_id).has_usages(self.options) |
| 316 | + } |
| 317 | +} |
| 318 | + |
| 319 | +pub(super) trait HasAnyUsedBinding<'a> { |
| 320 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool; |
| 321 | +} |
| 322 | + |
| 323 | +impl<'a> HasAnyUsedBinding<'a> for BindingPattern<'a> { |
| 324 | + #[inline] |
| 325 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { |
| 326 | + self.kind.has_any_used_binding(ctx) |
| 327 | + } |
| 328 | +} |
| 329 | +impl<'a> HasAnyUsedBinding<'a> for BindingPatternKind<'a> { |
| 330 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { |
| 331 | + match self { |
| 332 | + Self::BindingIdentifier(id) => id.has_any_used_binding(ctx), |
| 333 | + Self::AssignmentPattern(id) => id.left.has_any_used_binding(ctx), |
| 334 | + Self::ObjectPattern(id) => id.has_any_used_binding(ctx), |
| 335 | + Self::ArrayPattern(id) => id.has_any_used_binding(ctx), |
| 336 | + } |
| 337 | + } |
| 338 | +} |
| 339 | + |
| 340 | +impl<'a> HasAnyUsedBinding<'a> for BindingIdentifier<'a> { |
| 341 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { |
| 342 | + self.symbol_id.get().is_some_and(|symbol_id| ctx.has_usages(symbol_id)) |
| 343 | + } |
| 344 | +} |
| 345 | +impl<'a> HasAnyUsedBinding<'a> for ObjectPattern<'a> { |
| 346 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { |
| 347 | + if ctx.options.ignore_rest_siblings && self.rest.is_some() { |
| 348 | + return true; |
| 349 | + } |
| 350 | + self.properties.iter().any(|p| p.value.has_any_used_binding(ctx)) |
| 351 | + || self.rest.as_ref().map_or(false, |rest| rest.argument.has_any_used_binding(ctx)) |
| 352 | + } |
| 353 | +} |
| 354 | +impl<'a> HasAnyUsedBinding<'a> for ArrayPattern<'a> { |
| 355 | + fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { |
| 356 | + self.elements.iter().flatten().any(|el| { |
| 357 | + // if the destructured element is ignored, it is considered used |
| 358 | + el.get_identifier().is_some_and(|name| ctx.options.is_ignored_array_destructured(&name)) |
| 359 | + || el.has_any_used_binding(ctx) |
| 360 | + }) || self.rest.as_ref().map_or(false, |rest| rest.argument.has_any_used_binding(ctx)) |
| 361 | + } |
| 362 | +} |
0 commit comments