1use std::collections::hash_map::Entry;
2
3use rustc_hash::{FxHashMap, FxHashSet};
4use swc_atoms::{atom, Atom};
5use swc_common::{errors::HANDLER, Span, SyntaxContext};
6use swc_ecma_ast::*;
7use swc_ecma_utils::for_each_binding_ident;
8use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
9
10use crate::rule::Rule;
11
12macro_rules! check_dupe_args {
14 ($node:expr) => {{
15 let mut done = Vec::new();
19
20 let mut hash_mode = false;
21
22 let mut i1 = 0;
23 for_each_binding_ident($node, |id1| {
24 i1 += 1;
25
26 if !hash_mode {
27 let mut i2 = 0;
28 for_each_binding_ident($node, |id2| {
29 i2 += 1;
30
31 if hash_mode {
32 return;
33 } else if i2 >= 100 {
34 hash_mode = true;
37 }
38
39 if i1 >= i2 || done.contains(&i1) {
40 return;
41 }
42
43 if id1.ctxt == id2.ctxt && id1.sym == id2.sym {
44 done.push(i1);
45
46 emit_dupe_args_error(id1, id2);
47 }
48 });
49 }
50 });
51
52 if hash_mode {
53 let mut map = FxHashMap::default();
54
55 for_each_binding_ident($node, |id| {
56 match map.entry((id.sym.clone(), id.ctxt)) {
58 Entry::Occupied(v) => {
59 emit_dupe_args_error(v.get(), id);
60 }
61
62 Entry::Vacant(v) => {
63 v.insert(id.clone());
64 }
65 }
66 });
67 }
68 }};
69}
70
71pub fn critical_rules() -> Box<dyn Rule> {
72 Box::new(CriticalRules::default())
73}
74
75#[derive(Debug, Default)]
76struct CriticalRules {
77 const_vars: FxHashMap<Id, Span>,
79 import_binding: FxHashMap<Id, Span>,
80
81 bindings: FxHashMap<Id, BindingInfo>,
83 type_bindings: FxHashSet<Id>,
84
85 exports: FxHashMap<Atom, Span>,
87 export_assign: Option<Span>,
88
89 var_decl_kind: Option<VarDeclKind>,
91 is_pat_decl: bool,
92 lexical_function: bool,
93}
94
95#[derive(Debug, Default, Clone, Copy)]
96struct BindingInfo {
97 span: Span,
98 ctxt: SyntaxContext,
99 unique: bool,
100 is_function: bool,
101}
102
103impl Rule for CriticalRules {
104 fn lint_module(&mut self, program: &Module) {
105 program.visit_with(&mut TypeCollector {
107 type_bindings: &mut self.type_bindings,
108 });
109
110 let mut const_collector = ConstCollector {
111 const_vars: &mut self.const_vars,
112 import_binding: &mut self.import_binding,
113 var_decl_kind: None,
114 };
115 program.visit_children_with(&mut const_collector);
116
117 self.lexical_function = true;
119 self.visit_module(program);
120 }
121
122 fn lint_script(&mut self, program: &Script) {
123 program.visit_with(&mut TypeCollector {
125 type_bindings: &mut self.type_bindings,
126 });
127
128 let mut const_collector = ConstCollector {
129 const_vars: &mut self.const_vars,
130 import_binding: &mut self.import_binding,
131 var_decl_kind: None,
132 };
133 program.visit_children_with(&mut const_collector);
134
135 self.visit_script(program);
137 }
138}
139
140impl CriticalRules {
141 fn add_binding(&mut self, id: Atom, info: BindingInfo) {
143 match self.bindings.entry((id.clone(), info.ctxt)) {
144 Entry::Occupied(mut prev) => {
145 if !(info.is_function && prev.get().is_function)
146 && (info.unique || prev.get().unique)
147 {
148 emit_duplicate_binding_error(&id, info.span, prev.get().span);
149 }
150
151 if info.unique || !prev.get().unique {
152 *prev.get_mut() = info
153 }
154 }
155 Entry::Vacant(e) => {
156 e.insert(info);
157 }
158 }
159 }
160
161 fn is_unique_var_kind(&self) -> bool {
162 matches!(
163 self.var_decl_kind,
164 Some(VarDeclKind::Const) | Some(VarDeclKind::Let)
165 )
166 }
167
168 fn visit_with_kind<V: VisitWith<Self>>(&mut self, e: &V, kind: Option<VarDeclKind>) {
169 let old_var_decl_kind = self.var_decl_kind.take();
170 let old_is_pat_decl = self.is_pat_decl;
171
172 self.var_decl_kind = kind;
173 self.is_pat_decl = true;
174
175 e.visit_children_with(self);
176
177 self.is_pat_decl = old_is_pat_decl;
178 self.var_decl_kind = old_var_decl_kind;
179 }
180
181 fn visit_with_stmt_like<T: VisitWith<Self>, F: Fn(&T) -> Option<Ident>>(
182 &mut self,
183 s: &[T],
184 get_fn_ident: F,
185 ) {
186 let mut fn_name = FxHashMap::default();
187 for s in s {
188 if let Some(ident) = get_fn_ident(s) {
189 if let Some(prev) = fn_name.get(&ident.sym) {
190 emit_duplicate_binding_error(&ident.sym, ident.span, *prev)
191 } else {
192 fn_name.insert(ident.sym.clone(), ident.span);
193 }
194 }
195
196 s.visit_with(self);
197 }
198 }
199
200 fn visit_with_stmts(&mut self, s: &[Stmt], lexical_function: bool) {
201 let old = self.lexical_function;
202 self.lexical_function = lexical_function;
203
204 if lexical_function {
205 self.visit_with_stmt_like(s, |s| match s {
206 Stmt::Decl(Decl::Fn(FnDecl {
207 ident, function: f, ..
208 })) if f.body.is_some() => Some(ident.clone()),
209 _ => None,
210 });
211 } else {
212 s.visit_children_with(self);
213 }
214 self.lexical_function = old;
215 }
216
217 fn add_export(&mut self, id: &Ident) {
219 match self.exports.entry(id.sym.clone()) {
220 Entry::Occupied(mut prev) => {
221 let name = &id.sym;
222
223 HANDLER.with(|handler| {
224 handler
225 .struct_span_err(
226 id.span,
227 &format!("the name `{name}` is exported multiple times"),
228 )
229 .span_label(*prev.get(), "previous exported here")
230 .span_label(id.span, "exported more than once")
231 .note("Exported identifiers must be unique")
232 .emit();
233 });
234
235 *prev.get_mut() = id.span;
236 }
237 Entry::Vacant(e) => {
238 e.insert(id.span);
239 }
240 }
241
242 self.check_no_coexist();
243 }
244
245 fn add_export_assign(&mut self, span: Span) {
246 if let Some(prev_span) = self.export_assign {
247 HANDLER.with(|handler| {
248 handler
249 .struct_span_err(span, "multiple `export =` found")
250 .span_label(prev_span, "previous `export =` declared here")
251 .emit()
252 });
253 }
254
255 self.export_assign = Some(span);
256
257 self.check_no_coexist();
258 }
259
260 fn check_no_coexist(&self) {
261 if let Some(span) = self.export_assign {
262 if !self.exports.is_empty() {
263 HANDLER.with(|handler| {
264 handler
265 .struct_span_err(span, r#"An export assignment cannot be used in a module with other exported elements."#)
266 .emit()
267 });
268 }
269 }
270 }
271
272 fn check_const_assign(&self, id: &Ident) {
274 if self.is_pat_decl {
275 return;
276 }
277
278 if let Some(&decl_span) = self.const_vars.get(&id.to_id()) {
279 HANDLER.with(|handler| {
280 handler
281 .struct_span_err(
282 id.span,
283 "cannot reassign to a variable declared with `const`",
284 )
285 .span_label(decl_span, "const variable was declared here")
286 .span_suggestion(
287 decl_span,
288 "consider making this variable mutable",
289 format!("let {}", id.sym),
290 )
291 .span_label(id.span, "cannot reassign")
292 .emit();
293 });
294 }
295
296 if let Some(&binding_span) = self.import_binding.get(&id.to_id()) {
297 HANDLER.with(|handler| {
298 handler
299 .struct_span_err(id.span, "cannot reassign to an imported binding")
300 .span_label(binding_span, "imported binding")
301 .emit();
302 });
303 }
304 }
305}
306
307impl Visit for CriticalRules {
308 noop_visit_type!();
309
310 fn visit_module(&mut self, m: &Module) {
313 self.lexical_function = true;
314
315 self.visit_with_stmt_like(&m.body, |s| match s {
316 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
317 ident, function: f, ..
318 })))
319 | ModuleItem::ModuleDecl(
320 ModuleDecl::ExportDecl(ExportDecl {
321 decl:
322 Decl::Fn(FnDecl {
323 ident, function: f, ..
324 }),
325 ..
326 })
327 | ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
328 decl:
329 DefaultDecl::Fn(FnExpr {
330 ident: Some(ident),
331 function: f,
332 }),
333 ..
334 }),
335 ) if f.body.is_some() => Some(ident.clone()),
336 _ => None,
337 });
338 }
339
340 fn visit_script(&mut self, s: &Script) {
341 s.body.visit_children_with(self);
342 }
343
344 fn visit_arrow_expr(&mut self, a: &ArrowExpr) {
345 check_dupe_args!(&a.params);
347
348 let ArrowExpr { params, body, .. } = a;
349 params.visit_with(self);
350 if let BlockStmtOrExpr::BlockStmt(b) = &**body {
351 self.visit_with_stmts(&b.stmts, false)
352 }
353 }
354
355 fn visit_function(&mut self, f: &Function) {
356 check_dupe_args!(&f.params);
358
359 let Function {
360 body,
361 params,
362 decorators,
363 ..
364 } = f;
365 params.visit_with(self);
366 decorators.visit_with(self);
367 if let Some(body) = body {
368 self.visit_with_stmts(&body.stmts, false)
369 }
370 }
371
372 fn visit_constructor(&mut self, f: &Constructor) {
373 check_dupe_args!(&f.params);
375
376 f.visit_children_with(self);
377 }
378
379 fn visit_var_decl(&mut self, d: &VarDecl) {
380 if d.declare {
381 return;
382 }
383
384 self.visit_with_kind(d, Some(d.kind))
385 }
386
387 fn visit_var_declarator(&mut self, var_declarator: &VarDeclarator) {
388 let old_is_pat_decl = self.is_pat_decl;
389 self.is_pat_decl = true;
390 var_declarator.name.visit_with(self);
391 self.is_pat_decl = old_is_pat_decl;
392
393 var_declarator.init.visit_with(self);
394 }
395
396 fn visit_binding_ident(&mut self, n: &BindingIdent) {
397 self.check_const_assign(&Ident::from(n));
398 }
399
400 fn visit_update_expr(&mut self, n: &UpdateExpr) {
401 n.visit_children_with(self);
402
403 if let Expr::Ident(ident) = &*n.arg {
404 self.check_const_assign(ident);
405 }
406 }
407
408 fn visit_pat(&mut self, p: &Pat) {
409 p.visit_children_with(self);
410
411 if let Pat::Ident(p) = p {
412 if self.is_pat_decl {
413 self.add_binding(
414 p.sym.clone(),
415 BindingInfo {
416 span: p.span,
417 ctxt: p.ctxt,
418 unique: self.is_unique_var_kind(),
419 is_function: false,
420 },
421 );
422 }
423 }
424 }
425
426 fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
427 p.visit_children_with(self);
428
429 if self.is_pat_decl {
430 self.add_binding(
431 p.key.sym.clone(),
432 BindingInfo {
433 span: p.key.span,
434 ctxt: p.key.ctxt,
435 unique: self.is_unique_var_kind(),
436 is_function: false,
437 },
438 );
439 }
440 }
441
442 fn visit_expr(&mut self, e: &Expr) {
443 let old_var_decl_kind = self.var_decl_kind.take();
444 let old_is_pat_decl = self.is_pat_decl;
445
446 self.var_decl_kind = None;
447 self.is_pat_decl = false;
448
449 e.visit_children_with(self);
450
451 self.is_pat_decl = old_is_pat_decl;
452 self.var_decl_kind = old_var_decl_kind;
453 }
454
455 fn visit_class_decl(&mut self, d: &ClassDecl) {
456 if d.declare {
457 return;
458 }
459
460 self.add_binding(
461 d.ident.sym.clone(),
462 BindingInfo {
463 span: d.ident.span,
464 ctxt: d.ident.ctxt,
465 unique: true,
466 is_function: false,
467 },
468 );
469
470 d.visit_children_with(self);
471 }
472
473 fn visit_fn_decl(&mut self, d: &FnDecl) {
474 if d.function.body.is_none() || d.declare {
475 return;
476 }
477
478 self.add_binding(
479 d.ident.sym.clone(),
480 BindingInfo {
481 span: d.ident.span,
482 ctxt: d.ident.ctxt,
483 unique: self.lexical_function,
484 is_function: true,
485 },
486 );
487
488 d.visit_children_with(self);
489 }
490
491 fn visit_import_decl(&mut self, s: &ImportDecl) {
492 if s.type_only {
493 return;
494 }
495
496 s.visit_children_with(self);
497 }
498
499 fn visit_import_default_specifier(&mut self, s: &ImportDefaultSpecifier) {
500 s.visit_children_with(self);
501
502 if !self.type_bindings.contains(&s.local.to_id()) {
503 self.add_binding(
504 s.local.sym.clone(),
505 BindingInfo {
506 span: s.local.span,
507 ctxt: s.local.ctxt,
508 unique: true,
509 is_function: false,
510 },
511 );
512 }
513 }
514
515 fn visit_import_named_specifier(&mut self, s: &ImportNamedSpecifier) {
516 s.visit_children_with(self);
517
518 if !s.is_type_only && !self.type_bindings.contains(&s.local.to_id()) {
519 self.add_binding(
520 s.local.sym.clone(),
521 BindingInfo {
522 span: s.local.span,
523 ctxt: s.local.ctxt,
524 unique: true,
525 is_function: false,
526 },
527 );
528 }
529 }
530
531 fn visit_import_star_as_specifier(&mut self, s: &ImportStarAsSpecifier) {
532 s.visit_children_with(self);
533
534 if !self.type_bindings.contains(&s.local.to_id()) {
535 self.add_binding(
536 s.local.sym.clone(),
537 BindingInfo {
538 span: s.local.span,
539 ctxt: s.local.ctxt,
540 unique: true,
541 is_function: false,
542 },
543 );
544 }
545 }
546
547 fn visit_catch_clause(&mut self, c: &CatchClause) {
548 self.visit_with_kind(c, Some(VarDeclKind::Var))
549 }
550
551 fn visit_param(&mut self, p: &Param) {
552 self.visit_with_kind(p, Some(VarDeclKind::Var))
553 }
554
555 fn visit_static_block(&mut self, c: &StaticBlock) {
556 self.visit_with_stmts(&c.body.stmts, false)
557 }
558
559 fn visit_stmts(&mut self, b: &[Stmt]) {
560 self.visit_with_stmts(b, true)
561 }
562
563 fn visit_export_default_decl(&mut self, d: &ExportDefaultDecl) {
565 match &d.decl {
566 DefaultDecl::Class(ClassExpr {
567 ident: Some(ident), ..
568 }) => self.add_binding(
569 ident.sym.clone(),
570 BindingInfo {
571 span: ident.span,
572 ctxt: ident.ctxt,
573 unique: true,
574 is_function: false,
575 },
576 ),
577 DefaultDecl::Fn(FnExpr {
578 ident: Some(ident),
579 function: f,
580 ..
581 }) if f.body.is_some() => self.add_binding(
582 ident.sym.clone(),
583 BindingInfo {
584 span: ident.span,
585 ctxt: ident.ctxt,
586 unique: self.lexical_function,
587 is_function: true,
588 },
589 ),
590 _ => {}
591 }
592
593 if match &d.decl {
595 DefaultDecl::Fn(FnExpr { function: f, .. }) if f.body.is_none() => true,
596 DefaultDecl::TsInterfaceDecl(..) => true,
597 _ => false,
598 } {
599 return;
600 }
601
602 d.visit_children_with(self);
603
604 self.add_export(&Ident::new_no_ctxt(atom!("default"), d.span));
605 }
606
607 fn visit_export_default_expr(&mut self, d: &ExportDefaultExpr) {
608 d.visit_children_with(self);
609
610 match &*d.expr {
611 Expr::Fn(FnExpr { function: f, .. }) if f.body.is_none() => return,
612 _ => {}
613 }
614
615 self.add_export(&Ident::new_no_ctxt(atom!("default"), d.span));
616 }
617
618 fn visit_export_default_specifier(&mut self, s: &ExportDefaultSpecifier) {
619 self.add_export(&s.exported);
620 }
621
622 fn visit_export_named_specifier(&mut self, s: &ExportNamedSpecifier) {
623 let exported = match &s.exported {
624 Some(ModuleExportName::Ident(ident)) => Some(ident),
625 Some(ModuleExportName::Str(..)) => return,
626 _ => None,
627 };
628 let orig = match &s.orig {
629 ModuleExportName::Ident(ident) => ident,
630 ModuleExportName::Str(..) => return,
631 #[cfg(swc_ast_unknown)]
632 _ => return,
633 };
634 self.add_export(exported.as_ref().unwrap_or(&orig));
635 }
636
637 fn visit_export_namespace_specifier(&mut self, s: &ExportNamespaceSpecifier) {
638 match &s.name {
639 ModuleExportName::Ident(name) => self.add_export(name),
640 ModuleExportName::Str(..) => {}
641 #[cfg(swc_ast_unknown)]
642 _ => (),
643 };
644 }
645
646 fn visit_ts_export_assignment(&mut self, n: &TsExportAssignment) {
647 self.add_export_assign(n.span);
648 }
649
650 fn visit_ts_import_equals_decl(&mut self, n: &TsImportEqualsDecl) {
651 if n.is_export && !n.is_type_only {
652 self.add_export(&n.id)
653 }
654
655 if !n.is_type_only && !self.type_bindings.contains(&n.id.to_id()) {
656 self.add_binding(
657 n.id.sym.clone(),
658 BindingInfo {
659 span: n.id.span,
660 ctxt: n.id.ctxt,
661 unique: true,
662 is_function: false,
663 },
664 );
665 }
666 }
667
668 fn visit_ts_module_decl(&mut self, d: &TsModuleDecl) {
669 if !d.declare {
670 let old_exports = std::mem::take(&mut self.exports);
671 let old_export_assign = self.export_assign.take();
672 d.visit_children_with(self);
673 self.exports = old_exports;
674 self.export_assign = old_export_assign;
675 }
676 }
677}
678
679struct ConstCollector<'a> {
681 const_vars: &'a mut FxHashMap<Id, Span>,
682 import_binding: &'a mut FxHashMap<Id, Span>,
683 var_decl_kind: Option<VarDeclKind>,
684}
685
686impl Visit for ConstCollector<'_> {
687 noop_visit_type!();
688
689 fn visit_import_specifier(&mut self, n: &ImportSpecifier) {
690 match n {
691 ImportSpecifier::Named(ImportNamedSpecifier { local, .. })
692 | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. })
693 | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => {
694 self.import_binding.insert(local.to_id(), local.span);
695 }
696 #[cfg(swc_ast_unknown)]
697 _ => (),
698 }
699 }
700
701 fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
702 p.visit_children_with(self);
703
704 if let Some(VarDeclKind::Const) = self.var_decl_kind {
705 *self.const_vars.entry(p.key.to_id()).or_default() = p.span;
706 }
707 }
708
709 fn visit_expr(&mut self, e: &Expr) {
710 let old_var_decl_kind = self.var_decl_kind;
711 self.var_decl_kind = None;
712
713 e.visit_children_with(self);
714
715 self.var_decl_kind = old_var_decl_kind;
716 }
717
718 fn visit_pat(&mut self, p: &Pat) {
719 p.visit_children_with(self);
720
721 if let Some(VarDeclKind::Const) = self.var_decl_kind {
722 if let Pat::Ident(i) = p {
723 *self.const_vars.entry(i.to_id()).or_default() = i.span;
724 }
725 }
726 }
727
728 fn visit_var_decl(&mut self, var_decl: &VarDecl) {
729 let old_var_decl_kind = self.var_decl_kind;
730 self.var_decl_kind = Some(var_decl.kind);
731
732 var_decl.visit_children_with(self);
733
734 self.var_decl_kind = old_var_decl_kind;
735 }
736}
737
738struct TypeCollector<'a> {
739 type_bindings: &'a mut FxHashSet<Id>,
740}
741
742impl Visit for TypeCollector<'_> {
743 fn visit_ts_entity_name(&mut self, n: &TsEntityName) {
744 n.visit_children_with(self);
745
746 if let TsEntityName::Ident(ident) = n {
747 self.type_bindings.insert(ident.to_id());
748 }
749 }
750}
751
752#[cold]
753fn emit_dupe_args_error(first: &BindingIdent, second: &BindingIdent) {
754 HANDLER.with(|handler| {
755 handler
756 .struct_span_err(
757 second.span,
758 &format!(
759 "the name `{}` is bound more than once in this parameter list",
760 first.sym
761 ),
762 )
763 .span_label(first.span, "previous definition here".to_string())
764 .span_label(second.span, "used as parameter more than once".to_string())
765 .emit();
766 });
767}
768
769#[cold]
770fn emit_duplicate_binding_error(name: &str, span: Span, prev_span: Span) {
771 HANDLER.with(|handler| {
772 handler
773 .struct_span_err(
774 span,
775 &format!("the name `{name}` is defined multiple times"),
776 )
777 .span_label(prev_span, format!("previous definition of `{name}` here"))
778 .span_label(span, format!("`{name}` redefined here"))
779 .emit();
780 });
781}