diff --git a/crates/oxc_isolated_declarations/src/diagnostics.rs b/crates/oxc_isolated_declarations/src/diagnostics.rs index 4577147b7d380..6b6edfdd94652 100644 --- a/crates/oxc_isolated_declarations/src/diagnostics.rs +++ b/crates/oxc_isolated_declarations/src/diagnostics.rs @@ -112,7 +112,6 @@ pub fn implicitly_adding_undefined_to_type(span: Span) -> OxcDiagnostic { .with_label(span) } -#[allow(dead_code)] pub fn function_with_assigning_properties(span: Span) -> OxcDiagnostic { OxcDiagnostic::error( "TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function.", diff --git a/crates/oxc_isolated_declarations/src/lib.rs b/crates/oxc_isolated_declarations/src/lib.rs index 285b653b4f96c..b736e8ddbba1a 100644 --- a/crates/oxc_isolated_declarations/src/lib.rs +++ b/crates/oxc_isolated_declarations/src/lib.rs @@ -19,11 +19,13 @@ mod types; use std::{cell::RefCell, collections::VecDeque, mem}; +use diagnostics::function_with_assigning_properties; use oxc_allocator::Allocator; #[allow(clippy::wildcard_imports)] use oxc_ast::{ast::*, AstBuilder, Visit}; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{Atom, SourceType, SPAN}; +use rustc_hash::FxHashSet; use crate::scope::ScopeTree; @@ -105,6 +107,7 @@ impl<'a> IsolatedDeclarations<'a> { } } } + self.report_error_for_expando_function(stmts); new_ast_stmts } @@ -321,6 +324,7 @@ impl<'a> IsolatedDeclarations<'a> { .push(Statement::from(ModuleDeclaration::ExportNamedDeclaration(empty_export))); } + self.report_error_for_expando_function(stmts); new_ast_stmts } @@ -393,6 +397,85 @@ impl<'a> IsolatedDeclarations<'a> { }) } + pub fn report_error_for_expando_function(&self, stmts: &oxc_allocator::Vec<'a, Statement<'a>>) { + let mut can_expando_function_names = FxHashSet::default(); + for stmt in stmts { + match stmt { + Statement::ExportNamedDeclaration(decl) => match decl.declaration.as_ref() { + Some(Declaration::FunctionDeclaration(func)) => { + if func.body.is_some() { + if let Some(id) = func.id.as_ref() { + can_expando_function_names.insert(id.name.clone()); + } + } + } + Some(Declaration::VariableDeclaration(decl)) => { + for declarator in &decl.declarations { + if declarator.id.type_annotation.is_none() + && declarator.init.as_ref().is_some_and(Expression::is_function) + { + if let Some(name) = declarator.id.get_identifier() { + can_expando_function_names.insert(name.clone()); + } + } + } + } + _ => (), + }, + Statement::ExportDefaultDeclaration(decl) => { + if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = + &decl.declaration + { + if func.body.is_some() { + if let Some(id) = func.id.as_ref() { + can_expando_function_names.insert(id.name.clone()); + } + } + } + } + Statement::FunctionDeclaration(func) => { + if func.body.is_some() { + if let Some(id) = func.id.as_ref() { + if self.scope.has_reference(&id.name) { + can_expando_function_names.insert(id.name.clone()); + } + } + } + } + Statement::VariableDeclaration(decl) => { + for declarator in &decl.declarations { + if declarator.id.type_annotation.is_none() + && declarator.init.as_ref().is_some_and(Expression::is_function) + { + if let Some(name) = declarator.id.get_identifier() { + if self.scope.has_reference(&name) { + can_expando_function_names.insert(name.clone()); + } + } + } + } + } + Statement::ExpressionStatement(stmt) => { + if let Expression::AssignmentExpression(assignment) = &stmt.expression { + if let AssignmentTarget::StaticMemberExpression(static_member_expr) = + &assignment.left + { + if let Expression::Identifier(ident) = &static_member_expr.object { + if can_expando_function_names.contains(&ident.name) { + self.error(function_with_assigning_properties( + static_member_expr.span, + )); + } + } + } + } + } + + _ => {} + } + } + } + pub fn is_declare(&self) -> bool { // If we are in a module block, we don't need to add declare !self.scope.is_ts_module_block_flag() diff --git a/crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts b/crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts new file mode 100644 index 0000000000000..8633029cc6b0f --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts @@ -0,0 +1,15 @@ +export function foo(): void {} +foo.apply = () => {} + +export const bar = (): void => {} +bar.call = ()=> {} + + +export namespace NS { + export const goo = (): void => {} + goo.length = 10 +} + +// unexported +const zoo = (): void => {} +zoo.toString = ()=> {} \ No newline at end of file diff --git a/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap b/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap new file mode 100644 index 0000000000000..9f41e74cc5f6c --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap @@ -0,0 +1,44 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts +--- +==================== .D.TS ==================== + +export declare function foo(): void; +export declare const bar: () => void; +export declare module NS { + export const goo: () => void; +} + + +==================== Errors ==================== + + 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. + ,-[10:3] + 9 | export const goo = (): void => {} + 10 | goo.length = 10 + : ^^^^^^^^^^ + 11 | } + `---- + + 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. + ,-[2:1] + 1 | export function foo(): void {} + 2 | foo.apply = () => {} + : ^^^^^^^^^ + 3 | + `---- + + 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. + ,-[5:1] + 4 | export const bar = (): void => {} + 5 | bar.call = ()=> {} + : ^^^^^^^^ + 6 | + `----