mirror of
https://github.com/swc-project/swc.git
synced 2025-01-08 23:10:13 +03:00
feat(es/lints): Implement simple validations (#2763)
swc_ecma_lint: - Implement `const-assign`. (Closes #2016) - Implement `duplicate-bindings`. (Closes #2795) - Implement `duplicate-exports`. (Closes #3056) swc: - Use linter.
This commit is contained in:
parent
23742e3b18
commit
f21af5bcd6
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2498,6 +2498,7 @@ dependencies = [
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_codegen",
|
||||
"swc_ecma_ext_transforms",
|
||||
"swc_ecma_lints",
|
||||
"swc_ecma_loader",
|
||||
"swc_ecma_minifier",
|
||||
"swc_ecma_parser",
|
||||
@ -2783,6 +2784,20 @@ dependencies = [
|
||||
"swc_ecma_visit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_lints"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"auto_impl 0.5.0",
|
||||
"parking_lot 0.11.1",
|
||||
"rayon",
|
||||
"swc_atoms 0.2.9",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_utils",
|
||||
"swc_ecma_visit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_loader"
|
||||
version = "0.25.1"
|
||||
|
@ -4,6 +4,7 @@ members = [
|
||||
"crates/node",
|
||||
"crates/swc_css",
|
||||
"crates/swc_ecmascript",
|
||||
"crates/swc_ecma_lints",
|
||||
"crates/swc_ecma_plugin_ast",
|
||||
"crates/swc_estree_compat",
|
||||
"crates/swc_plugin",
|
||||
|
@ -56,6 +56,7 @@ swc_common = {version = "0.15.0", path = "../swc_common", features = ["sourcemap
|
||||
swc_ecma_ast = {version = "0.60.0", path = "../swc_ecma_ast"}
|
||||
swc_ecma_codegen = {version = "0.84.0", path = "../swc_ecma_codegen"}
|
||||
swc_ecma_ext_transforms = {version = "0.42.0", path = "../swc_ecma_ext_transforms"}
|
||||
swc_ecma_lints = {version = "0.1.0", path = "../swc_ecma_lints"}
|
||||
swc_ecma_loader = {version = "0.25.0", path = "../swc_ecma_loader", features = ["lru", "node", "tsc"]}
|
||||
swc_ecma_minifier = {version = "0.60.0", path = "../swc_ecma_minifier"}
|
||||
swc_ecma_parser = {version = "0.82.0", path = "../swc_ecma_parser"}
|
||||
|
@ -29,6 +29,7 @@ use swc_common::{
|
||||
};
|
||||
use swc_ecma_ast::{EsVersion, Expr, Program};
|
||||
use swc_ecma_ext_transforms::jest;
|
||||
use swc_ecma_lints::rules::lint_to_fold;
|
||||
use swc_ecma_loader::resolvers::{
|
||||
lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
|
||||
};
|
||||
@ -241,7 +242,7 @@ impl Default for InputSourceMap {
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// `parss`: `(syntax, target, is_module)`
|
||||
/// `parse`: `(syntax, target, is_module)`
|
||||
pub fn build_as_input<'a, P>(
|
||||
&self,
|
||||
cm: &Arc<SourceMap>,
|
||||
@ -383,6 +384,7 @@ impl Options {
|
||||
),
|
||||
syntax.typescript()
|
||||
),
|
||||
lint_to_fold(swc_ecma_lints::rules::all()),
|
||||
crate::plugin::plugins(experimental.plugins),
|
||||
custom_before_pass(&program),
|
||||
// handle jsx
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use swc::{
|
||||
config::{IsModule, Options},
|
||||
Compiler,
|
||||
try_with_handler, Compiler,
|
||||
};
|
||||
use swc_common::{sync::Lrc, FilePathMapping, SourceMap};
|
||||
use testing::{NormalizedOutput, Tester};
|
||||
|
||||
fn file(f: impl AsRef<Path>) -> NormalizedOutput {
|
||||
@ -42,3 +43,37 @@ fn issue_1532() {
|
||||
|
||||
assert!(f.contains("unknown variant `esnext`"))
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/errors/**/input.js")]
|
||||
fn fixture(input: PathBuf) {
|
||||
let _log = testing::init();
|
||||
let output_path = input.parent().unwrap().join("output.swc-stderr");
|
||||
|
||||
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let err = try_with_handler(cm.clone(), true, |handler| {
|
||||
let c = Compiler::new(cm.clone());
|
||||
|
||||
let fm = cm.load_file(&input).expect("failed to load file");
|
||||
|
||||
match c.process_js_file(
|
||||
fm,
|
||||
&handler,
|
||||
&Options {
|
||||
swcrc: true,
|
||||
is_module: IsModule::Bool(true),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
) {
|
||||
Ok(..) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.expect_err("should fail");
|
||||
|
||||
let output = NormalizedOutput::from(format!("{:?}", err));
|
||||
|
||||
output.compare_to_file(&output_path).unwrap();
|
||||
}
|
||||
|
5
crates/swc/tests/errors/lints/const-assign/1/input.js
Normal file
5
crates/swc/tests/errors/lints/const-assign/1/input.js
Normal file
@ -0,0 +1,5 @@
|
||||
const a = 500;
|
||||
|
||||
|
||||
|
||||
a = 600;
|
@ -0,0 +1,12 @@
|
||||
error: Cannot reassign to a variable declared with `const`
|
||||
|
||||
|
|
||||
5 | a = 600;
|
||||
| ^
|
||||
|
|
||||
note: a was declared here
|
||||
|
||||
|
|
||||
1 | const a = 500;
|
||||
| ^
|
||||
|
@ -0,0 +1,2 @@
|
||||
const a = 5;
|
||||
let a = 2;
|
@ -0,0 +1,12 @@
|
||||
error: Duplicate binding
|
||||
|
||||
|
|
||||
2 | let a = 2;
|
||||
| ^
|
||||
|
|
||||
note: a was declared at here
|
||||
|
||||
|
|
||||
1 | const a = 5;
|
||||
| ^
|
||||
|
@ -0,0 +1,2 @@
|
||||
import { hi } from 'foo';
|
||||
import { hi } from 'foo';
|
@ -0,0 +1,12 @@
|
||||
error: Duplicate binding
|
||||
|
||||
|
|
||||
2 | import { hi } from 'foo';
|
||||
| ^^
|
||||
|
|
||||
note: hi was declared at here
|
||||
|
||||
|
|
||||
1 | import { hi } from 'foo';
|
||||
| ^^
|
||||
|
@ -0,0 +1,7 @@
|
||||
export default () => {
|
||||
let a = 2;
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let b = 2;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
error: Duplicate export
|
||||
|
||||
|
|
||||
5 | / export default () => {
|
||||
6 | | let b = 2;
|
||||
7 | | }
|
||||
| |_^
|
||||
|
|
||||
note: default was exported at here
|
||||
|
||||
|
|
||||
1 | / export default () => {
|
||||
2 | | let a = 2;
|
||||
3 | | }
|
||||
| |_^
|
||||
|
@ -0,0 +1,7 @@
|
||||
const { a } = 1;
|
||||
|
||||
export { a as default };
|
||||
|
||||
export default () => {
|
||||
let b = 2;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
error: Duplicate export
|
||||
|
||||
|
|
||||
5 | / export default () => {
|
||||
6 | | let b = 2;
|
||||
7 | | }
|
||||
| |_^
|
||||
|
|
||||
note: default was exported at here
|
||||
|
||||
|
|
||||
3 | export { a as default };
|
||||
| ^^^^^^^
|
||||
|
19
crates/swc_ecma_lints/Cargo.toml
Normal file
19
crates/swc_ecma_lints/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "Linter for the swc project"
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
name = "swc_ecma_lints"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.1.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
auto_impl = "0.5.0"
|
||||
parking_lot = "0.11"
|
||||
rayon = "1.5.1"
|
||||
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
|
||||
swc_common = {version = "0.15.1", path = "../swc_common"}
|
||||
swc_ecma_ast = {version = "0.60.0", path = "../swc_ecma_ast"}
|
||||
swc_ecma_utils = {version = "0.56.1", path = "../swc_ecma_utils"}
|
||||
swc_ecma_visit = {version = "0.46.0", path = "../swc_ecma_visit"}
|
2
crates/swc_ecma_lints/src/lib.rs
Normal file
2
crates/swc_ecma_lints/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod rule;
|
||||
pub mod rules;
|
102
crates/swc_ecma_lints/src/rule.rs
Normal file
102
crates/swc_ecma_lints/src/rule.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use auto_impl::auto_impl;
|
||||
use parking_lot::Mutex;
|
||||
use rayon::prelude::*;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use swc_common::errors::{Diagnostic, DiagnosticBuilder, Emitter, Handler, HANDLER};
|
||||
use swc_ecma_ast::{Module, Script};
|
||||
use swc_ecma_visit::{Visit, VisitWith};
|
||||
|
||||
/// A lint rule.
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// Must report error to [swc_common::HANDLER]
|
||||
#[auto_impl(Box, &mut)]
|
||||
pub trait Rule: Debug + Send + Sync {
|
||||
fn lint_module(&mut self, program: &Module);
|
||||
fn lint_script(&mut self, program: &Script);
|
||||
}
|
||||
|
||||
macro_rules! for_vec {
|
||||
($name:ident, $program:ident, $s:expr) => {{
|
||||
let program = $program;
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
for rule in $s {
|
||||
rule.$name(program);
|
||||
}
|
||||
} else {
|
||||
let errors = $s
|
||||
.par_iter_mut()
|
||||
.flat_map(|rule| {
|
||||
let emitter = Capturing::default();
|
||||
{
|
||||
let handler = Handler::with_emitter(true, false, Box::new(emitter.clone()));
|
||||
HANDLER.set(&handler, || {
|
||||
rule.$name(program);
|
||||
});
|
||||
}
|
||||
|
||||
let errors = Arc::try_unwrap(emitter.errors).unwrap().into_inner();
|
||||
|
||||
errors
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
HANDLER.with(|handler| {
|
||||
for error in errors {
|
||||
DiagnosticBuilder::new_diagnostic(&handler, error).emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// This preserves the order of errors.
|
||||
impl<R> Rule for Vec<R>
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
fn lint_module(&mut self, program: &Module) {
|
||||
for_vec!(lint_module, program, self)
|
||||
}
|
||||
|
||||
fn lint_script(&mut self, program: &Script) {
|
||||
for_vec!(lint_script, program, self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct Capturing {
|
||||
errors: Arc<Mutex<Vec<Diagnostic>>>,
|
||||
}
|
||||
|
||||
impl Emitter for Capturing {
|
||||
fn emit(&mut self, db: &DiagnosticBuilder<'_>) {
|
||||
self.errors.lock().push((**db).clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn visitor_rule<V>(v: V) -> Box<dyn Rule>
|
||||
where
|
||||
V: 'static + Send + Sync + Visit + Default + Debug,
|
||||
{
|
||||
Box::new(VisitorRule(v))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VisitorRule<V>(V)
|
||||
where
|
||||
V: Send + Sync + Visit;
|
||||
|
||||
impl<V> Rule for VisitorRule<V>
|
||||
where
|
||||
V: Send + Sync + Visit + Debug,
|
||||
{
|
||||
fn lint_module(&mut self, program: &Module) {
|
||||
program.visit_with(&mut self.0);
|
||||
}
|
||||
|
||||
fn lint_script(&mut self, program: &Script) {
|
||||
program.visit_with(&mut self.0);
|
||||
}
|
||||
}
|
127
crates/swc_ecma_lints/src/rules/const_assign.rs
Normal file
127
crates/swc_ecma_lints/src/rules/const_assign.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use crate::rule::{visitor_rule, Rule};
|
||||
use swc_common::{collections::AHashMap, errors::HANDLER, Span};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
|
||||
|
||||
pub fn const_assign() -> Box<dyn Rule> {
|
||||
visitor_rule(ConstAssign::default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ConstAssign {
|
||||
const_vars: AHashMap<Id, Span>,
|
||||
|
||||
is_pat_decl: bool,
|
||||
}
|
||||
|
||||
impl ConstAssign {
|
||||
fn check(&mut self, id: &Ident) {
|
||||
if self.is_pat_decl {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&decl_span) = self.const_vars.get(&id.to_id()) {
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(
|
||||
id.span,
|
||||
"Cannot reassign to a variable declared with `const`",
|
||||
)
|
||||
.span_note(decl_span, &format!("{} was declared here", id.sym))
|
||||
.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for ConstAssign {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
self.check(&p.key);
|
||||
}
|
||||
|
||||
fn visit_module(&mut self, program: &Module) {
|
||||
program.visit_children_with(&mut Collector {
|
||||
const_vars: &mut self.const_vars,
|
||||
var_decl_kind: None,
|
||||
});
|
||||
|
||||
program.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &Pat) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
match p {
|
||||
Pat::Ident(p) => {
|
||||
self.check(&p.id);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_script(&mut self, program: &Script) {
|
||||
program.visit_children_with(&mut Collector {
|
||||
const_vars: &mut self.const_vars,
|
||||
var_decl_kind: None,
|
||||
});
|
||||
|
||||
program.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_var_decl(&mut self, var_decl: &VarDecl) {
|
||||
let old_is_pat_decl = self.is_pat_decl;
|
||||
self.is_pat_decl = true;
|
||||
var_decl.visit_children_with(self);
|
||||
self.is_pat_decl = old_is_pat_decl;
|
||||
}
|
||||
}
|
||||
|
||||
struct Collector<'a> {
|
||||
const_vars: &'a mut AHashMap<Id, Span>,
|
||||
|
||||
var_decl_kind: Option<VarDeclKind>,
|
||||
}
|
||||
|
||||
impl Visit for Collector<'_> {
|
||||
fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
if let Some(VarDeclKind::Const) = self.var_decl_kind {
|
||||
*self.const_vars.entry(p.key.to_id()).or_default() = p.span;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &Expr) {
|
||||
let old_var_decl_kind = self.var_decl_kind;
|
||||
self.var_decl_kind = None;
|
||||
|
||||
e.visit_children_with(self);
|
||||
|
||||
self.var_decl_kind = old_var_decl_kind;
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &Pat) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
if let Some(VarDeclKind::Const) = self.var_decl_kind {
|
||||
if let Pat::Ident(i) = p {
|
||||
*self.const_vars.entry(i.to_id()).or_default() = i.id.span;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_var_decl(&mut self, var_decl: &VarDecl) {
|
||||
let old_var_decl_kind = self.var_decl_kind;
|
||||
self.var_decl_kind = Some(var_decl.kind);
|
||||
|
||||
var_decl.visit_children_with(self);
|
||||
|
||||
self.var_decl_kind = old_var_decl_kind;
|
||||
}
|
||||
}
|
129
crates/swc_ecma_lints/src/rules/duplicate_bindings.rs
Normal file
129
crates/swc_ecma_lints/src/rules/duplicate_bindings.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use swc_common::{collections::AHashMap, errors::HANDLER, Span};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, Rule};
|
||||
|
||||
pub fn duplicate_bindings() -> Box<dyn Rule> {
|
||||
visitor_rule(DuplicateBindings::default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct DuplicateBindings {
|
||||
bindings: AHashMap<Id, Span>,
|
||||
|
||||
var_decl_kind: Option<VarDeclKind>,
|
||||
is_pat_decl: bool,
|
||||
}
|
||||
|
||||
impl DuplicateBindings {
|
||||
/// Add a binding.
|
||||
fn add(&mut self, id: &Ident, check_for_var_kind: bool) {
|
||||
if check_for_var_kind {
|
||||
if let Some(VarDeclKind::Var) = self.var_decl_kind {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match self.bindings.entry(id.to_id()) {
|
||||
Entry::Occupied(mut prev) => {
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(id.span, "Duplicate binding")
|
||||
.span_note(*prev.get(), &format!("{} was declared at here", id.sym))
|
||||
.emit();
|
||||
});
|
||||
|
||||
// Next span.
|
||||
*prev.get_mut() = id.span;
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(id.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for DuplicateBindings {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
if self.is_pat_decl {
|
||||
self.add(&p.key, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_class_decl(&mut self, d: &ClassDecl) {
|
||||
self.add(&d.ident, false);
|
||||
|
||||
d.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &Expr) {
|
||||
let old_var_decl_kind = self.var_decl_kind.take();
|
||||
let old_is_pat_decl = self.is_pat_decl;
|
||||
|
||||
self.var_decl_kind = None;
|
||||
self.is_pat_decl = false;
|
||||
|
||||
e.visit_children_with(self);
|
||||
|
||||
self.is_pat_decl = old_is_pat_decl;
|
||||
self.var_decl_kind = old_var_decl_kind;
|
||||
}
|
||||
|
||||
fn visit_fn_decl(&mut self, d: &FnDecl) {
|
||||
self.add(&d.ident, false);
|
||||
|
||||
d.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_import_default_specifier(&mut self, s: &ImportDefaultSpecifier) {
|
||||
s.visit_children_with(self);
|
||||
|
||||
self.add(&s.local, false);
|
||||
}
|
||||
|
||||
fn visit_import_named_specifier(&mut self, s: &ImportNamedSpecifier) {
|
||||
s.visit_children_with(self);
|
||||
|
||||
self.add(&s.local, false);
|
||||
}
|
||||
|
||||
fn visit_import_star_as_specifier(&mut self, s: &ImportStarAsSpecifier) {
|
||||
s.visit_children_with(self);
|
||||
|
||||
self.add(&s.local, false);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &Pat) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
match p {
|
||||
Pat::Ident(p) => {
|
||||
if self.is_pat_decl {
|
||||
self.add(&p.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_var_decl(&mut self, d: &VarDecl) {
|
||||
let old_var_decl_kind = self.var_decl_kind.take();
|
||||
let old_is_pat_decl = self.is_pat_decl;
|
||||
|
||||
self.var_decl_kind = Some(d.kind);
|
||||
self.is_pat_decl = true;
|
||||
|
||||
d.visit_children_with(self);
|
||||
|
||||
self.is_pat_decl = old_is_pat_decl;
|
||||
self.var_decl_kind = old_var_decl_kind;
|
||||
}
|
||||
}
|
66
crates/swc_ecma_lints/src/rules/duplicate_exports.rs
Normal file
66
crates/swc_ecma_lints/src/rules/duplicate_exports.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use swc_atoms::{js_word, JsWord};
|
||||
use swc_common::{collections::AHashMap, errors::HANDLER, Span};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, Rule};
|
||||
|
||||
pub fn duplicate_exports() -> Box<dyn Rule> {
|
||||
visitor_rule(DuplicateExports::default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct DuplicateExports {
|
||||
exports: AHashMap<JsWord, Span>,
|
||||
}
|
||||
|
||||
impl DuplicateExports {
|
||||
/// Add an export.
|
||||
fn add(&mut self, id: &Ident) {
|
||||
match self.exports.entry(id.sym.clone()) {
|
||||
Entry::Occupied(mut prev) => {
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(id.span, "Duplicate export")
|
||||
.span_note(*prev.get(), &format!("{} was exported at here", id.sym))
|
||||
.emit();
|
||||
});
|
||||
|
||||
// Next span.
|
||||
*prev.get_mut() = id.span;
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(id.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for DuplicateExports {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_export_default_decl(&mut self, d: &ExportDefaultDecl) {
|
||||
d.visit_children_with(self);
|
||||
|
||||
self.add(&Ident::new(js_word!("default"), d.span));
|
||||
}
|
||||
|
||||
fn visit_export_default_expr(&mut self, d: &ExportDefaultExpr) {
|
||||
d.visit_children_with(self);
|
||||
|
||||
self.add(&Ident::new(js_word!("default"), d.span));
|
||||
}
|
||||
|
||||
fn visit_export_default_specifier(&mut self, s: &ExportDefaultSpecifier) {
|
||||
self.add(&s.exported);
|
||||
}
|
||||
|
||||
fn visit_export_named_specifier(&mut self, s: &ExportNamedSpecifier) {
|
||||
self.add(&s.exported.as_ref().unwrap_or(&s.orig));
|
||||
}
|
||||
|
||||
fn visit_export_namespace_specifier(&mut self, s: &ExportNamespaceSpecifier) {
|
||||
self.add(&s.name);
|
||||
}
|
||||
}
|
47
crates/swc_ecma_lints/src/rules/mod.rs
Normal file
47
crates/swc_ecma_lints/src/rules/mod.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::rule::Rule;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_visit::{noop_fold_type, Fold};
|
||||
|
||||
mod const_assign;
|
||||
mod duplicate_bindings;
|
||||
mod duplicate_exports;
|
||||
|
||||
pub fn all() -> Vec<Box<dyn Rule>> {
|
||||
vec![
|
||||
const_assign::const_assign(),
|
||||
duplicate_bindings::duplicate_bindings(),
|
||||
duplicate_exports::duplicate_exports(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn lint_to_fold<R>(r: R) -> impl Fold
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
LintFolder(r)
|
||||
}
|
||||
|
||||
struct LintFolder<R>(R)
|
||||
where
|
||||
R: Rule;
|
||||
|
||||
impl<R> Fold for LintFolder<R>
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
noop_fold_type!();
|
||||
|
||||
#[inline(always)]
|
||||
fn fold_module(&mut self, program: Module) -> Module {
|
||||
self.0.lint_module(&program);
|
||||
|
||||
program
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn fold_script(&mut self, program: Script) -> Script {
|
||||
self.0.lint_script(&program);
|
||||
|
||||
program
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user