fix(isolated_declarations): Support expando functions (#4910)

I mentioned this issue in a
https://github.com/oxc-project/oxc/issues/4607#issuecomment-2264863618
but I later realized this is actually a separate issue from the one that
I originally reported.

It seems that https://github.com/oxc-project/oxc/pull/3872 added support
for reporting expando function errors, but didn't add support for cases
where expando functions are allowed. This PR adds support for not
reporting errors when there is a namespace declaration that declares the
variable being assigned to.

[TypeScript
playground](https://www.typescriptlang.org/play/?noCheck=true&isolatedDeclarations=true&ts=5.5.4#code/KYDwDg9gTgLgBAMwK4DsDGMCWEWIhACgEoAuOANwkwBM4BvAXwCgF8A6AQzDABsBPOAF44xIQD56zJqEiw4aHAGd4AIw5QhI0hSq1BExkzVQ2aDjx6bR+yUzszo8FBwC2wRWA5pgcAHIBleiY4OAc5BRRlOABzfCttShpxWxDYiDYeYBRomAALTQBGAAYmKTCnV3dPbzwIIJDM1XVNABYAJgBuYNDwRzhGuDUAL0Kioq6pVnTjVs6WdmHR8bsAehW4VHLgaiYIqKG44WIyRL0DZgP0mAh-GChMbPjkxiA)
for reference

---------

Co-authored-by: MichaelMitchell-at <=>
This commit is contained in:
michaelm 2024-08-15 10:12:16 -04:00 committed by GitHub
parent 2476dceee0
commit 4a16916887
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 107 additions and 5 deletions

View file

@ -26,7 +26,7 @@ use oxc_allocator::Allocator;
use oxc_ast::{ast::*, AstBuilder, Visit};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{Atom, SourceType, SPAN};
use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::scope::ScopeTree;
@ -404,7 +404,76 @@ impl<'a> IsolatedDeclarations<'a> {
})
}
fn get_assignable_properties_for_namespaces(
stmts: &'a oxc_allocator::Vec<'a, Statement<'a>>,
) -> FxHashMap<&'a str, FxHashSet<Atom>> {
let mut assignable_properties_for_namespace = FxHashMap::<&str, FxHashSet<Atom>>::default();
for stmt in stmts {
let Statement::ExportNamedDeclaration(decl) = stmt else { continue };
let Some(Declaration::TSModuleDeclaration(decl)) = &decl.declaration else { continue };
if decl.kind != TSModuleDeclarationKind::Namespace {
continue;
}
let TSModuleDeclarationName::Identifier(ident) = &decl.id else { continue };
let Some(TSModuleDeclarationBody::TSModuleBlock(block)) = &decl.body else {
continue;
};
for stmt in &block.body {
let Statement::ExportNamedDeclaration(decl) = stmt else { continue };
match &decl.declaration {
Some(Declaration::VariableDeclaration(var)) => {
for declarator in &var.declarations {
if let Some(name) = declarator.id.get_identifier() {
assignable_properties_for_namespace
.entry(&ident.name)
.or_default()
.insert(name);
}
}
}
Some(Declaration::FunctionDeclaration(func)) => {
if let Some(id) = func.id.as_ref() {
assignable_properties_for_namespace
.entry(&ident.name)
.or_default()
.insert(id.name.clone());
}
}
Some(Declaration::ClassDeclaration(cls)) => {
if let Some(id) = cls.id.as_ref() {
assignable_properties_for_namespace
.entry(&ident.name)
.or_default()
.insert(id.name.clone());
}
}
Some(Declaration::TSEnumDeclaration(decl)) => {
assignable_properties_for_namespace
.entry(&ident.name)
.or_default()
.insert(decl.id.name.clone());
}
Some(Declaration::UsingDeclaration(decl)) => {
for declarator in &decl.declarations {
if let Some(name) = declarator.id.get_identifier() {
assignable_properties_for_namespace
.entry(&ident.name)
.or_default()
.insert(name);
}
}
}
_ => {}
}
}
}
assignable_properties_for_namespace
}
pub fn report_error_for_expando_function(&self, stmts: &oxc_allocator::Vec<'a, Statement<'a>>) {
let assignable_properties_for_namespace =
IsolatedDeclarations::get_assignable_properties_for_namespaces(stmts);
let mut can_expando_function_names = FxHashSet::default();
for stmt in stmts {
match stmt {
@ -468,7 +537,13 @@ impl<'a> IsolatedDeclarations<'a> {
&assignment.left
{
if let Expression::Identifier(ident) = &static_member_expr.object {
if can_expando_function_names.contains(&ident.name) {
if can_expando_function_names.contains(&ident.name)
&& !assignable_properties_for_namespace
.get(&ident.name.as_str())
.map_or(false, |properties| {
properties.contains(&static_member_expr.property.name)
})
{
self.error(function_with_assigning_properties(
static_member_expr.span,
));

View file

@ -2,7 +2,7 @@ export function foo(): void {}
foo.apply = () => {}
export const bar = (): void => {}
bar.call = ()=> {}
bar.call = () => {}
export namespace NS {
@ -10,6 +10,20 @@ export namespace NS {
goo.length = 10
}
export namespace foo {
// declaration must be exported
let bar = 42;
export let baz = 100;
}
// namespace must be exported
namespace foo {
export let bar = 42;
}
foo.bar = 42;
foo.baz = 100;
// unexported
const zoo = (): void => {}
zoo.toString = ()=> {}
zoo.toString = () => {}

View file

@ -9,6 +9,9 @@ export declare const bar: () => void;
export declare namespace NS {
export const goo: () => void;
}
export declare namespace foo {
export let baz: number;
}
==================== Errors ====================
@ -38,7 +41,17 @@ export declare namespace NS {
| properties assigned to this function.
,-[5:1]
4 | export const bar = (): void => {}
5 | bar.call = ()=> {}
5 | bar.call = () => {}
: ^^^^^^^^
6 |
`----
x TS9023: Assigning properties to functions without declaring them is not
| supported with --isolatedDeclarations. Add an explicit declaration for the
| properties assigned to this function.
,-[24:1]
23 |
24 | foo.bar = 42;
: ^^^^^^^
25 | foo.baz = 100;
`----