Skip to content

Commit 7ac6b47

Browse files
committed
feat(isolated-declarations): report error for expando functions
1 parent 8c61f9c commit 7ac6b47

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

crates/oxc_isolated_declarations/src/diagnostics.rs

-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ pub fn implicitly_adding_undefined_to_type(span: Span) -> OxcDiagnostic {
112112
.with_label(span)
113113
}
114114

115-
#[allow(dead_code)]
116115
pub fn function_with_assigning_properties(span: Span) -> OxcDiagnostic {
117116
OxcDiagnostic::error(
118117
"TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function.",

crates/oxc_isolated_declarations/src/lib.rs

+84
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ mod types;
1919

2020
use std::{cell::RefCell, collections::VecDeque, mem};
2121

22+
use diagnostics::function_with_assigning_properties;
2223
use oxc_allocator::Allocator;
2324
#[allow(clippy::wildcard_imports)]
2425
use oxc_ast::{ast::*, AstBuilder, Visit};
2526
use oxc_diagnostics::OxcDiagnostic;
2627
use oxc_span::{Atom, SourceType, SPAN};
28+
use rustc_hash::FxHashSet;
2729

2830
use crate::scope::ScopeTree;
2931

@@ -105,6 +107,7 @@ impl<'a> IsolatedDeclarations<'a> {
105107
}
106108
}
107109
}
110+
self.report_error_for_expando_function(stmts);
108111
new_ast_stmts
109112
}
110113

@@ -119,6 +122,7 @@ impl<'a> IsolatedDeclarations<'a> {
119122
let mut variables_declarations = VecDeque::new();
120123
let mut variable_transformed_indexes = VecDeque::new();
121124
let mut transformed_indexes = Vec::new();
125+
122126
// 1. Collect all declarations, module declarations
123127
// 2. Transform export declarations
124128
// 3. Collect all bindings / reference from module declarations
@@ -321,6 +325,7 @@ impl<'a> IsolatedDeclarations<'a> {
321325
.push(Statement::from(ModuleDeclaration::ExportNamedDeclaration(empty_export)));
322326
}
323327

328+
self.report_error_for_expando_function(stmts);
324329
new_ast_stmts
325330
}
326331

@@ -393,6 +398,85 @@ impl<'a> IsolatedDeclarations<'a> {
393398
})
394399
}
395400

401+
pub fn report_error_for_expando_function(&self, stmts: &oxc_allocator::Vec<'a, Statement<'a>>) {
402+
let mut can_expando_function_names = FxHashSet::default();
403+
for stmt in stmts {
404+
match stmt {
405+
Statement::ExportNamedDeclaration(decl) => match decl.declaration.as_ref() {
406+
Some(Declaration::FunctionDeclaration(func)) => {
407+
if func.body.is_some() {
408+
if let Some(id) = func.id.as_ref() {
409+
can_expando_function_names.insert(id.name.clone());
410+
}
411+
}
412+
}
413+
Some(Declaration::VariableDeclaration(decl)) => {
414+
for declarator in &decl.declarations {
415+
if declarator.id.type_annotation.is_none()
416+
&& declarator.init.as_ref().is_some_and(Expression::is_function)
417+
{
418+
if let Some(name) = declarator.id.get_identifier() {
419+
can_expando_function_names.insert(name.clone());
420+
}
421+
}
422+
}
423+
}
424+
_ => (),
425+
},
426+
Statement::ExportDefaultDeclaration(decl) => {
427+
if let ExportDefaultDeclarationKind::FunctionDeclaration(func) =
428+
&decl.declaration
429+
{
430+
if func.body.is_some() {
431+
if let Some(id) = func.id.as_ref() {
432+
can_expando_function_names.insert(id.name.clone());
433+
}
434+
}
435+
}
436+
}
437+
Statement::FunctionDeclaration(func) => {
438+
if func.body.is_some() {
439+
if let Some(id) = func.id.as_ref() {
440+
if self.scope.has_reference(&id.name) {
441+
can_expando_function_names.insert(id.name.clone());
442+
}
443+
}
444+
}
445+
}
446+
Statement::VariableDeclaration(decl) => {
447+
for declarator in &decl.declarations {
448+
if declarator.id.type_annotation.is_none()
449+
&& declarator.init.as_ref().is_some_and(Expression::is_function)
450+
{
451+
if let Some(name) = declarator.id.get_identifier() {
452+
if self.scope.has_reference(&name) {
453+
can_expando_function_names.insert(name.clone());
454+
}
455+
}
456+
}
457+
}
458+
}
459+
Statement::ExpressionStatement(stmt) => {
460+
if let Expression::AssignmentExpression(assignment) = &stmt.expression {
461+
if let AssignmentTarget::StaticMemberExpression(static_member_expr) =
462+
&assignment.left
463+
{
464+
if let Expression::Identifier(ident) = &static_member_expr.object {
465+
if can_expando_function_names.contains(&ident.name) {
466+
self.error(function_with_assigning_properties(
467+
static_member_expr.span,
468+
));
469+
}
470+
}
471+
}
472+
}
473+
}
474+
475+
_ => {}
476+
}
477+
}
478+
}
479+
396480
pub fn is_declare(&self) -> bool {
397481
// If we are in a module block, we don't need to add declare
398482
!self.scope.is_ts_module_block_flag()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function foo(): void {}
2+
foo.apply = () => {}
3+
4+
export const bar = (): void => {}
5+
bar.call = ()=> {}
6+
7+
8+
export namespace NS {
9+
export const goo = (): void => {}
10+
goo.length = 10
11+
}
12+
13+
// unexported
14+
const zoo = (): void => {}
15+
zoo.toString = ()=> {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
source: crates/oxc_isolated_declarations/tests/mod.rs
3+
input_file: crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts
4+
---
5+
==================== .D.TS ====================
6+
7+
export declare function foo(): void;
8+
export declare const bar: () => void;
9+
export declare module NS {
10+
export const goo: () => void;
11+
}
12+
13+
14+
==================== Errors ====================
15+
16+
x TS9023: Assigning properties to functions without declaring them is not
17+
| supported with --isolatedDeclarations. Add an explicit declaration for the
18+
| properties assigned to this function.
19+
,-[10:3]
20+
9 | export const goo = (): void => {}
21+
10 | goo.length = 10
22+
: ^^^^^^^^^^
23+
11 | }
24+
`----
25+
26+
x TS9023: Assigning properties to functions without declaring them is not
27+
| supported with --isolatedDeclarations. Add an explicit declaration for the
28+
| properties assigned to this function.
29+
,-[2:1]
30+
1 | export function foo(): void {}
31+
2 | foo.apply = () => {}
32+
: ^^^^^^^^^
33+
3 |
34+
`----
35+
36+
x TS9023: Assigning properties to functions without declaring them is not
37+
| supported with --isolatedDeclarations. Add an explicit declaration for the
38+
| properties assigned to this function.
39+
,-[5:1]
40+
4 | export const bar = (): void => {}
41+
5 | bar.call = ()=> {}
42+
: ^^^^^^^^
43+
6 |
44+
`----

0 commit comments

Comments
 (0)