diff --git a/crates/oxc_linter/src/rules/early_error/javascript.rs b/crates/oxc_linter/src/rules/early_error/javascript.rs index 5d95b0e6e..4c8bd1cf7 100644 --- a/crates/oxc_linter/src/rules/early_error/javascript.rs +++ b/crates/oxc_linter/src/rules/early_error/javascript.rs @@ -39,7 +39,12 @@ impl Rule for EarlyErrorJavaScript { AstKind::RegExpLiteral(lit) => check_regexp_literal(lit, ctx), AstKind::Directive(dir) => check_directive(dir, node, ctx), - AstKind::ModuleDeclaration(decl) => check_module_declaration(decl, node, ctx), + AstKind::ModuleDeclaration(decl) => { + check_module_declaration(decl, node, ctx); + if let ModuleDeclarationKind::ImportDeclaration(import_decl) = &decl.kind { + check_import_declaration(import_decl, ctx); + } + } AstKind::MetaProperty(prop) => check_meta_property(prop, node, ctx), AstKind::WithStatement(stmt) => check_with_statement(stmt, node, ctx), @@ -70,6 +75,18 @@ impl Rule for EarlyErrorJavaScript { } } +fn check_duplicate_bound_names(bound_names: &T, ctx: &LintContext) { + // bound_names are usually small, a simple loop should be more performant checking with a hashmap + let mut idents = bound_names.bound_names(); + idents.sort_unstable_by_key(|ident| ident.name.as_str()); + for i in 1..idents.len() { + let ident = &idents[i - 1]; + if let Some(found) = idents[i..].iter().find(|i| i.name == ident.name) { + ctx.diagnostic(Redeclaration(ident.name.clone(), ident.span, found.span)); + } + } +} + #[derive(Debug, Error, Diagnostic)] #[error("Cannot use await in class static initialization block")] #[diagnostic()] @@ -393,6 +410,13 @@ fn check_module_declaration<'a>( } } +fn check_import_declaration(decl: &ImportDeclaration, ctx: &LintContext) { + // ModuleItem : ImportDeclaration + // It is a Syntax Error if the BoundNames of ImportDeclaration contains any duplicate entries. + // bound_names are usually small, a simple loop should be more performant checking with a hashmap + check_duplicate_bound_names(decl, ctx); +} + fn check_meta_property<'a>(prop: &MetaProperty, node: &AstNode<'a>, ctx: &LintContext<'a>) { #[derive(Debug, Error, Diagnostic)] #[error("Unexpected new.target expression")] @@ -868,15 +892,7 @@ fn check_formal_parameters<'a>( return; } - // bound_names are usually small, a simple loop should be more performant checking with a hashmap - let mut idents = params.bound_names(); - idents.sort_unstable_by_key(|ident| ident.name.as_str()); - for i in 1..idents.len() { - let ident = &idents[i - 1]; - if let Some(found) = idents[i..].iter().find(|i| i.name == ident.name) { - ctx.diagnostic(Redeclaration(ident.name.clone(), ident.span, found.span)); - } - } + check_duplicate_bound_names(params, ctx); } fn check_formal_parameter(param: &FormalParameter, ctx: &LintContext) { diff --git a/tasks/coverage/test262.snap b/tasks/coverage/test262.snap index 6dad5fbe2..073e721f1 100644 --- a/tasks/coverage/test262.snap +++ b/tasks/coverage/test262.snap @@ -1,8 +1,7 @@ Test262 Summary: AST Parsed : 44015/44034 (99.96%) Positive Passed: 44015/44034 (99.96%) -Negative Passed: 3907/3917 (99.74%) -Expect Syntax Error: "language/import/dup-bound-names.js" +Negative Passed: 3908/3917 (99.77%) Expect Syntax Error: "language/import/json-invalid.js" Expect Syntax Error: "language/import/json-named-bindings.js" Expect Syntax Error: "language/module-code/early-dup-export-as-star-as.js" @@ -18949,6 +18948,15 @@ Expect to Parse: "language/statements/function/S14_A5_T2.js" · ╰── Invalid Character `ⸯ` ╰──── + × Identifier `x` has already been declared + ╭─[language/import/dup-bound-names.js:15:1] + 15 │ + 16 │ import { x, y as x } from 'z'; + · ┬ ┬ + · │ ╰── It can not be redeclared here + · ╰── `x` has already been declared here + ╰──── + × Keywords cannot contain escape characters ╭─[language/import/escaped-as-import-specifier.js:25:1] 25 │