mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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:
parent
2476dceee0
commit
4a16916887
3 changed files with 107 additions and 5 deletions
|
|
@ -26,7 +26,7 @@ use oxc_allocator::Allocator;
|
||||||
use oxc_ast::{ast::*, AstBuilder, Visit};
|
use oxc_ast::{ast::*, AstBuilder, Visit};
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_span::{Atom, SourceType, SPAN};
|
use oxc_span::{Atom, SourceType, SPAN};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::scope::ScopeTree;
|
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>>) {
|
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();
|
let mut can_expando_function_names = FxHashSet::default();
|
||||||
for stmt in stmts {
|
for stmt in stmts {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
|
@ -468,7 +537,13 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||||
&assignment.left
|
&assignment.left
|
||||||
{
|
{
|
||||||
if let Expression::Identifier(ident) = &static_member_expr.object {
|
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(
|
self.error(function_with_assigning_properties(
|
||||||
static_member_expr.span,
|
static_member_expr.span,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,20 @@ export namespace NS {
|
||||||
goo.length = 10
|
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
|
// unexported
|
||||||
const zoo = (): void => {}
|
const zoo = (): void => {}
|
||||||
zoo.toString = () => {}
|
zoo.toString = () => {}
|
||||||
|
|
@ -9,6 +9,9 @@ export declare const bar: () => void;
|
||||||
export declare namespace NS {
|
export declare namespace NS {
|
||||||
export const goo: () => void;
|
export const goo: () => void;
|
||||||
}
|
}
|
||||||
|
export declare namespace foo {
|
||||||
|
export let baz: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
==================== Errors ====================
|
==================== Errors ====================
|
||||||
|
|
@ -42,3 +45,13 @@ export declare namespace NS {
|
||||||
: ^^^^^^^^
|
: ^^^^^^^^
|
||||||
6 |
|
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;
|
||||||
|
`----
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue