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
23 changes: 18 additions & 5 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,23 @@ impl<'a> SemanticBuilder<'a> {
let symbol_id = self.scoping.get_binding(scope_id, name).or_else(|| {
self.hoisting_variables.get(&scope_id).and_then(|symbols| symbols.get(name).copied())
})?;

// `(function n(n) {})()`
// ^ is not a redeclaration
// Since we put the function expression binding 'n' Symbol into the function itself scope,
// then defining a variable with the same name as the function name will be considered
// a redeclaration, but it's actually not a redeclaration, so if the symbol declaration
// is a function expression, then return None to tell the caller that it's not a redeclaration.
if self.scoping.symbol_flags(symbol_id).is_function()
&& self
.nodes
.kind(self.scoping.symbol_declaration(symbol_id))
.as_function()
.is_some_and(Function::is_expression)
{
return None;
}

if report_error {
self.check_and_report_redeclaration(name, span, symbol_id, excludes);
}
Expand All @@ -447,11 +464,7 @@ impl<'a> SemanticBuilder<'a> {
None
};

if (
flags.intersects(excludes)
// `function n() { let n; }`
// ^ is not a redeclaration
&& !function_kind.is_some_and(Function::is_expression))
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;`
Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_semantic/tests/integration/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,12 @@ fn test_class_merging() {
.contains_flags(SymbolFlags::Interface)
.test();
}

#[test]
fn test_redeclaration() {
SemanticTester::js("(function n(n) { console.log (n) }) ()")
.has_symbol("n") // the first `n` is the function expression
.equal_flags(SymbolFlags::Function) // param `n` is a SymbolFlags::FunctionScopedVariable
.has_number_of_references(0) // no references to the function
.test();
}
23 changes: 21 additions & 2 deletions crates/oxc_semantic/tests/integration/util/symbol_tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ impl<'a> SymbolTester<'a> {
) -> Self {
let symbols_with_target_name: Option<SymbolId> = semantic
.scoping()
.iter_bindings()
.find_map(|(_, bindings)| bindings.get(target).copied());
.symbol_ids()
.find(|&symbol_id| semantic.scoping().symbol_name(symbol_id) == target);

let data = match symbols_with_target_name {
Some(symbol_id) => Ok(symbol_id),
Expand Down Expand Up @@ -153,6 +153,25 @@ impl<'a> SymbolTester<'a> {
self
}

/// Checks if the resolved symbol has exactly the flags in `flags`, using [`SymbolFlags::eq()`]
pub fn equal_flags(mut self, flags: SymbolFlags) -> Self {
self.test_result = match self.test_result {
Ok(symbol_id) => {
let found_flags = self.semantic.scoping().symbol_flags(symbol_id);
if found_flags == flags {
Ok(symbol_id)
} else {
Err(OxcDiagnostic::error(format!(
"Expected {} to equal flags {:?}, but it is {:?}",
self.target_symbol_name, flags, found_flags
)))
}
}
err => err,
};
self
}

/// Check that this symbol has a certain number of read [`Reference`]s
///
/// References that are both read and write are counted.
Expand Down