Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/oxlint/fixtures/import/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import * as foo from './foo';
console.log(foo);

// import/no-default-export
export default function foo() {}
export default function () { }


Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ working directory: fixtures/import
x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/import/no-default-export.html\eslint-plugin-import(no-default-export)]8;;\: Prefer named exports
,-[test.js:7:8]
6 | // import/no-default-export
7 | export default function foo() {}
7 | export default function () { }
: ^^^^^^^
8 |
`----
Expand All @@ -28,7 +28,7 @@ working directory: fixtures/import
x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/import/no-default-export.html\eslint-plugin-import(no-default-export)]8;;\: Prefer named exports
,-[test.js:7:8]
6 | // import/no-default-export
7 | export default function foo() {}
7 | export default function () { }
: ^^^^^^^
8 |
`----
Expand Down
38 changes: 7 additions & 31 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use oxc_syntax::{

use crate::{
JSDocFinder, Semantic,
binder::{Binder, is_function_redeclared_not_allowed},
binder::Binder,
checker,
class::ClassTableBuilder,
diagnostics::redeclaration,
Expand Down Expand Up @@ -443,38 +443,14 @@ impl<'a> SemanticBuilder<'a> {
}

if report_error {
self.check_and_report_redeclaration(name, span, symbol_id, excludes);
let flags = self.scoping.symbol_flags(symbol_id);
if flags.intersects(excludes) {
let symbol_span = self.scoping.symbol_span(symbol_id);
self.error(redeclaration(name, symbol_span, span));
}
}
Some(symbol_id)
}

/// Check if a symbol with the same name has already been declared but
/// it actually is not allowed to be redeclared.
fn check_and_report_redeclaration(
&self,
name: &str,
span: Span,
symbol_id: SymbolId,
excludes: SymbolFlags,
) {
let flags = self.scoping.symbol_flags(symbol_id);
let function_kind = if flags.is_function() {
self.nodes.kind(self.scoping.symbol_declaration(symbol_id)).as_function()
} else {
None
};

if flags.intersects(excludes)
// Needs to further check if the previous declaration is a function and the function
// is not allowed to be redeclared.
// For example: `async function goo(); var goo;`
// ^^^ Redeclare
|| (excludes == SymbolFlags::FunctionScopedVariableExcludes &&
function_kind.is_some_and(|func| is_function_redeclared_not_allowed(func, self)))
{
let symbol_span = self.scoping.symbol_span(symbol_id);
self.error(redeclaration(name, symbol_span, span));
}
Some(symbol_id)
}

/// Declare an unresolved reference in the current scope.
Expand Down
63 changes: 62 additions & 1 deletion crates/oxc_semantic/src/checker/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use rustc_hash::FxHashMap;

use oxc_ast::{AstKind, ast::*};
use oxc_diagnostics::{LabeledSpan, OxcDiagnostic};
use oxc_ecmascript::{IsSimpleParameterList, PropName};
use oxc_ecmascript::{BoundNames, IsSimpleParameterList, PropName};
use oxc_span::{GetSpan, ModuleKind, Span};
use oxc_syntax::{
number::NumberBase,
operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator},
scope::ScopeFlags,
symbol::SymbolFlags,
};

use crate::{AstNode, builder::SemanticBuilder, diagnostics::redeclaration};
Expand Down Expand Up @@ -431,6 +432,66 @@ pub fn check_function_declaration<'a>(
};
}

// It is a Syntax Error if any element of the LexicallyDeclaredNames of
// StatementList also occurs in the VarDeclaredNames of StatementList.
pub fn check_variable_declarator_redeclaration(
decl: &VariableDeclarator,
ctx: &SemanticBuilder<'_>,
) {
if decl.kind != VariableDeclarationKind::Var || ctx.current_scope_flags().is_top() {
return;
}

decl.id.bound_names(&mut |ident| {
let redeclarations = ctx.scoping.symbol_redeclarations(ident.symbol_id());
let Some(rd) = redeclarations.iter().nth_back(1) else { return };

// `{ function f() {}; var f; }` is invalid in both strict and non-strict mode
if rd.flags.is_function() {
ctx.error(redeclaration(&ident.name, rd.span, decl.span));
}
});
}

// It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries,
// unless the source text matched by this production is not strict mode code
// and the duplicate entries are only bound by FunctionDeclarations.
// https://tc39.es/ecma262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics
pub fn check_function_redeclaration(func: &Function, ctx: &SemanticBuilder<'_>) {
let Some(id) = &func.id else { return };
let symbol_id = id.symbol_id();

let redeclarations = ctx.scoping.symbol_redeclarations(symbol_id);
let Some(prev) = redeclarations.iter().nth_back(1) else {
// No redeclarations
return;
};

// Already checked in `check_redelcaration`, because it is also not allowed in TypeScript
// `let a; function a() {}` is invalid in both strict and non-strict mode
if prev.flags.contains(SymbolFlags::BlockScopedVariable) {
return;
}

let current_scope_flags = ctx.current_scope_flags();
if !current_scope_flags.is_strict_mode() {
if current_scope_flags.intersects(ScopeFlags::Top | ScopeFlags::Function)
&& prev.flags.intersects(SymbolFlags::FunctionScopedVariable | SymbolFlags::Function)
{
// `function a() {}; function a() {}` and `var a; function a() {}` is invalid in non-strict mode
return;
} else if !(func.r#async || func.generator) {
// `{ var a; function a() {} }` is invalid in both strict and non-strict mode
let prev_function = ctx.nodes.kind(prev.declaration).as_function();
if prev_function.is_some_and(|func| !(func.r#async || func.generator)) {
return;
}
}
}

ctx.error(redeclaration(&id.name, prev.span, id.span));
}

fn reg_exp_flag_u_and_v(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error(
"The 'u' and 'v' regular expression flags cannot be enabled at the same time",
Expand Down
12 changes: 11 additions & 1 deletion crates/oxc_semantic/src/checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::{AstNode, builder::SemanticBuilder};
pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) {
let kind = node.kind();

let is_typescript = ctx.source_type.is_typescript();

match kind {
AstKind::Program(_) => {
js::check_duplicate_class_elements(ctx);
Expand Down Expand Up @@ -76,6 +78,9 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) {
js::check_class(class, node, ctx);
ts::check_class(class, ctx);
}
AstKind::Function(func) if !is_typescript => {
js::check_function_redeclaration(func, ctx);
}
AstKind::MethodDefinition(method) => {
js::check_method_definition(method, ctx);
ts::check_method_definition(method, ctx);
Expand Down Expand Up @@ -105,7 +110,12 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) {
AstKind::UnaryExpression(expr) => js::check_unary_expression(expr, ctx),
AstKind::YieldExpression(expr) => js::check_yield_expression(expr, node, ctx),
AstKind::VariableDeclaration(decl) => ts::check_variable_declaration(decl, ctx),
AstKind::VariableDeclarator(decl) => ts::check_variable_declarator(decl, ctx),
AstKind::VariableDeclarator(decl) => {
if !is_typescript {
js::check_variable_declarator_redeclaration(decl, ctx);
}
ts::check_variable_declarator(decl, ctx);
}
AstKind::SimpleAssignmentTarget(target) => ts::check_simple_assignment_target(target, ctx),
AstKind::TSInterfaceDeclaration(decl) => ts::check_ts_interface_declaration(decl, ctx),
AstKind::TSTypeAnnotation(annot) => ts::check_ts_type_annotation(annot, ctx),
Expand Down
76 changes: 73 additions & 3 deletions tasks/coverage/snapshots/parser_babel.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ commit: 578ac4df
parser_babel Summary:
AST Parsed : 2303/2322 (99.18%)
Positive Passed: 2282/2322 (98.28%)
Negative Passed: 1549/1673 (92.59%)
Negative Passed: 1551/1673 (92.71%)
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-if-body/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-loop/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/invalid-startindex-and-startline-specified-without-startcolumn/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/startline-and-startcolumn-specified/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/startline-specified/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-catch-func/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-var-sloppy/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/class-methods/direct-super-in-object-method/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/destructuring/error-operator-for-default/input.js
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/input.js
Expand Down Expand Up @@ -275,6 +273,18 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022
× Identifier `x` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/es2022/class-static-block/duplicate-function-var-name/input.js:3:11]
2 │ static {
3 │ var x;
· ┬
· ╰── `x` has already been declared here
4 │ function x() {}
· ┬
· ╰── It can not be redeclared here
5 │ }
╰────

× Identifier `x` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/es2022/class-static-block/duplicate-function-var-name/input.js:3:11]
2 │ static {
3 │ var x;
· ┬
· ╰── `x` has already been declared here
Expand Down Expand Up @@ -1247,6 +1257,18 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
· ╰── `foo` has already been declared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-catch-func/input.js:2:10]
1 │ try {
2 │ } catch (foo) {
· ─┬─
· ╰── `foo` has already been declared here
3 │ function foo() {}
· ─┬─
· ╰── It can not be redeclared here
4 │ }
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-catch-let/input.js:2:10]
1 │ try {
Expand Down Expand Up @@ -1323,6 +1345,16 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
· ╰── It can not be redeclared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-class-func/input.js:1:7]
1 │ class foo {};
· ─┬─
· ╰── `foo` has already been declared here
2 │ function foo () {};
· ─┬─
· ╰── It can not be redeclared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-class-let/input.js:1:7]
1 │ class foo {};
Expand Down Expand Up @@ -1359,6 +1391,24 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
· ╰── `f` has already been declared here
╰────

× Identifier `f` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-gen/input.js:1:12]
1 │ { function f() {} function* f() {} }
· ┬ ┬
· │ ╰── It can not be redeclared here
· ╰── `f` has already been declared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-module/input.js:1:10]
1 │ function foo() {}
· ─┬─
· ╰── `foo` has already been declared here
2 │ function foo() {}
· ─┬─
· ╰── It can not be redeclared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-module/input.js:1:10]
1 │ function foo() {}
Expand All @@ -1377,6 +1427,26 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
· ╰── `foo` has already been declared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-module-sloppy/input.js:1:12]
1 │ { function foo() {} function foo() {} }
· ─┬─ ─┬─
· │ ╰── It can not be redeclared here
· ╰── `foo` has already been declared here
╰────

× Identifier `foo` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-func-var-sloppy/input.js:2:12]
1 │ {
2 │ function foo() {}
· ─┬─
· ╰── `foo` has already been declared here
3 │ var foo = 1;
· ───┬───
· ╰── It can not be redeclared here
4 │ }
╰────

× Identifier `f` has already been declared
╭─[babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-gen-func/input.js:1:13]
1 │ { function* f() {} function f() {} }
Expand Down
Loading