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
62 changes: 14 additions & 48 deletions crates/oxc_semantic/src/binder.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
//! Declare symbol for `BindingIdentifier`s

use std::ptr;

use oxc_allocator::{Address, GetAddress};
use oxc_ast::{AstKind, ast::*};
use oxc_ecmascript::{BoundNames, IsSimpleParameterList};
use oxc_span::GetSpan;
use oxc_syntax::{
node::NodeId,
scope::{ScopeFlags, ScopeId},
symbol::SymbolFlags,
};
use oxc_syntax::{node::NodeId, scope::ScopeFlags, symbol::SymbolFlags};

use crate::SemanticBuilder;
use crate::checker::is_function_part_of_if_statement;

pub trait Binder<'a> {
#[expect(unused_variables)]
Expand Down Expand Up @@ -138,27 +133,6 @@ impl<'a> Binder<'a> for Class<'a> {
}
}

/// Check for Annex B `if (foo) function a() {} else function b() {}`
fn is_function_part_of_if_statement(function: &Function, builder: &SemanticBuilder) -> bool {
if builder.current_scope_flags().is_strict_mode() {
return false;
}
let AstKind::IfStatement(stmt) = builder.nodes.parent_kind(builder.current_node_id) else {
return false;
};
if let Statement::FunctionDeclaration(func) = &stmt.consequent {
if ptr::eq(func.as_ref(), function) {
return true;
}
}
if let Some(Statement::FunctionDeclaration(func)) = &stmt.alternate {
if ptr::eq(func.as_ref(), function) {
return true;
}
}
false
}

impl<'a> Binder<'a> for Function<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let includes = if self.declare {
Expand All @@ -168,27 +142,19 @@ impl<'a> Binder<'a> for Function<'a> {
};

if let Some(ident) = &self.id {
if is_function_part_of_if_statement(self, builder) {
let symbol_id = builder.scoping.create_symbol(
ident.span,
ident.name.into(),
includes,
ScopeId::new(u32::MAX - 1), // Not bound to any scope.
builder.current_node_id,
);
ident.symbol_id.set(Some(symbol_id));
let excludes = if builder.source_type.is_typescript() {
SymbolFlags::FunctionExcludes
} else if is_function_part_of_if_statement(self, builder) {
SymbolFlags::empty()
} else {
let excludes = if builder.source_type.is_typescript() {
SymbolFlags::FunctionExcludes
} else {
// `var x; function x() {}` is valid in non-strict mode, but `TypeScript`
// doesn't care about non-strict mode, so we need to exclude this,
// and further check in checker.
SymbolFlags::FunctionExcludes - SymbolFlags::FunctionScopedVariable
};
let symbol_id = builder.declare_symbol(ident.span, &ident.name, includes, excludes);
ident.symbol_id.set(Some(symbol_id));
}
// `var x; function x() {}` is valid in non-strict mode, but `TypeScript`
// doesn't care about non-strict mode, so we need to exclude this,
// and further check in checker.
SymbolFlags::FunctionExcludes - SymbolFlags::FunctionScopedVariable
};

let symbol_id = builder.declare_symbol(ident.span, &ident.name, includes, excludes);
ident.symbol_id.set(Some(symbol_id));
}

// Bind scope flags: GetAccessor | SetAccessor
Expand Down
28 changes: 28 additions & 0 deletions crates/oxc_semantic/src/checker/javascript.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ptr;

use phf::{Set, phf_set};
use rustc_hash::FxHashMap;

Expand Down Expand Up @@ -475,12 +477,38 @@ pub fn check_variable_declarator_redeclaration(
});
}

/// Check for Annex B `if (foo) function a() {} else function b() {}`
pub fn is_function_part_of_if_statement(function: &Function, builder: &SemanticBuilder) -> bool {
if builder.current_scope_flags().is_strict_mode() {
return false;
}
let AstKind::IfStatement(stmt) = builder.nodes.parent_kind(builder.current_node_id) else {
return false;
};
if let Statement::FunctionDeclaration(func) = &stmt.consequent {
if ptr::eq(func.as_ref(), function) {
return true;
}
}
if let Some(Statement::FunctionDeclaration(func)) = &stmt.alternate {
if ptr::eq(func.as_ref(), function) {
return true;
}
}
false
}

// 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 };

if is_function_part_of_if_statement(func, ctx) {
return;
}

let symbol_id = id.symbol_id();

let redeclarations = ctx.scoping.symbol_redeclarations(symbol_id);
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_semantic/src/checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use javascript as js;
use typescript as ts;

use crate::builder::SemanticBuilder;
pub use javascript::is_function_part_of_if_statement;

pub fn check<'a>(kind: AstKind<'a>, ctx: &SemanticBuilder<'a>) {
match kind {
Expand Down
20 changes: 1 addition & 19 deletions tasks/coverage/snapshots/semantic_babel.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ commit: 1d4546bc

semantic_babel Summary:
AST Parsed : 2362/2362 (100.00%)
Positive Passed: 1945/2362 (82.35%)
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/3.3-function-in-if-body/input.js
Symbol scope ID mismatch for "f":
after transform: SymbolId(0): ScopeId(4294967294)
rebuilt : SymbolId(0): ScopeId(4294967294)
Symbol scope ID mismatch for "g":
after transform: SymbolId(1): ScopeId(4294967294)
rebuilt : SymbolId(1): ScopeId(4294967294)

Positive Passed: 1948/2362 (82.47%)
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/decorators/decorators-after-export/input.js
Symbol span mismatch for "C":
after transform: SymbolId(0): Span { start: 65, end: 66 }
Expand All @@ -37,11 +29,6 @@ semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/op
Unexpected new.target expression
Unexpected new.target expression

semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/scope/dupl-bind-catch-hang-func/input.js
Symbol scope ID mismatch for "foo":
after transform: SymbolId(1): ScopeId(4294967294)
rebuilt : SymbolId(1): ScopeId(4294967294)

semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/sourcetype-unambiguous/typescript-type-only/input.ts
Bindings mismatch:
after transform: ScopeId(0): ["Foo"]
Expand Down Expand Up @@ -118,11 +105,6 @@ Identifier `x` has already been declared
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/top-level-await-unambiguous/module/input.js
`await` is only allowed within async functions and at the top levels of modules

semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/esprima/statement-if/migrated_0003/input.js
Symbol scope ID mismatch for "a":
after transform: SymbolId(0): ScopeId(4294967294)
rebuilt : SymbolId(0): ScopeId(4294967294)

semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/class-method/typescript/input.ts
Scope children mismatch:
after transform: ScopeId(1): [ScopeId(2)]
Expand Down
Loading
Loading