swc_ecma_lints/rules/
critical_rules.rs

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
12// Helper macro for checking duplicate arguments
13macro_rules! check_dupe_args {
14    ($node:expr) => {{
15        // This vector allocates only if there are duplicate parameters.
16        // This is used to handle the case where the same parameter is used 3 or more
17        // times.
18        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                        // While iterating for the first `id1`, we detect that there are more than
35                        // 100 identifiers. We switch to hash mode.
36                        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                //
57                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    // For const_assign
78    const_vars: FxHashMap<Id, Span>,
79    import_binding: FxHashMap<Id, Span>,
80
81    // For duplicate_bindings
82    bindings: FxHashMap<Id, BindingInfo>,
83    type_bindings: FxHashSet<Id>,
84
85    // For duplicate_exports
86    exports: FxHashMap<Atom, Span>,
87    export_assign: Option<Span>,
88
89    // Shared state
90    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        // First pass: collect type bindings and const variables
106        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        // Reset state and do main visit
118        self.lexical_function = true;
119        self.visit_module(program);
120    }
121
122    fn lint_script(&mut self, program: &Script) {
123        // First pass: collect type bindings and const variables
124        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        // Reset state and do main visit
136        self.visit_script(program);
137    }
138}
139
140impl CriticalRules {
141    // Helper methods from duplicate_bindings
142    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    // Helper methods from duplicate_exports
218    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    // Helper methods from const_assign
273    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    // Visit methods for all rules combined
311
312    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 for duplicate args
346        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 for duplicate args
357        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 for duplicate args
374        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    // Export related visits
564    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        // Check for duplicate exports
594        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
679// Collector structs
680struct 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}