oxc_isolated_declarations/
lib.rs1use std::{cell::RefCell, mem};
9
10use rustc_hash::{FxHashMap, FxHashSet};
11
12use oxc_allocator::{Allocator, CloneIn, Vec as ArenaVec};
13use oxc_ast::{AstBuilder, NONE, ast::*};
14use oxc_ast_visit::Visit;
15use oxc_diagnostics::OxcDiagnostic;
16use oxc_span::{Atom, GetSpan, SPAN, SourceType};
17
18use crate::{diagnostics::function_with_assigning_properties, scope::ScopeTree};
19
20mod class;
21mod declaration;
22mod diagnostics;
23mod r#enum;
24mod formal_parameter_binding_pattern;
25mod function;
26mod inferrer;
27mod literal;
28mod module;
29mod return_type;
30mod scope;
31mod signatures;
32mod types;
33
34#[derive(Debug, Default, Clone, Copy)]
35pub struct IsolatedDeclarationsOptions {
36    pub strip_internal: bool,
45}
46
47#[non_exhaustive]
48pub struct IsolatedDeclarationsReturn<'a> {
49    pub program: Program<'a>,
50    pub errors: Vec<OxcDiagnostic>,
51}
52
53pub struct IsolatedDeclarations<'a> {
54    ast: AstBuilder<'a>,
55
56    scope: ScopeTree<'a>,
58    errors: RefCell<Vec<OxcDiagnostic>>,
59
60    strip_internal: bool,
62
63    internal_annotations: FxHashSet<u32>,
65}
66
67impl<'a> IsolatedDeclarations<'a> {
68    pub fn new(allocator: &'a Allocator, options: IsolatedDeclarationsOptions) -> Self {
69        let strip_internal = options.strip_internal;
70        Self {
71            ast: AstBuilder::new(allocator),
72            strip_internal,
73            internal_annotations: FxHashSet::default(),
74            scope: ScopeTree::new(),
75            errors: RefCell::new(vec![]),
76        }
77    }
78
79    pub fn build(mut self, program: &Program<'a>) -> IsolatedDeclarationsReturn<'a> {
83        self.internal_annotations = self
84            .strip_internal
85            .then(|| Self::build_internal_annotations(program))
86            .unwrap_or_default();
87        let source_type = SourceType::d_ts();
88        let directives = self.ast.vec();
89        let stmts = self.transform_program(program);
90        let program = self.ast.program(
91            SPAN,
92            source_type,
93            program.source_text,
94            self.ast.vec_from_iter(program.comments.iter().copied()),
95            None,
96            directives,
97            stmts,
98        );
99        IsolatedDeclarationsReturn { program, errors: self.take_errors() }
100    }
101
102    fn take_errors(&self) -> Vec<OxcDiagnostic> {
103        mem::take(&mut self.errors.borrow_mut())
104    }
105
106    fn error(&self, error: OxcDiagnostic) {
108        self.errors.borrow_mut().push(error);
109    }
110
111    fn build_internal_annotations(program: &Program<'a>) -> FxHashSet<u32> {
113        let mut set = FxHashSet::default();
114        for comment in &program.comments {
115            let has_internal =
116                comment.content_span().source_text(program.source_text).contains("@internal");
117            if has_internal && !set.contains(&comment.attached_to) {
119                set.insert(comment.attached_to);
120            }
121        }
122        set
123    }
124
125    fn has_internal_annotation(&self, span: Span) -> bool {
127        if !self.strip_internal {
128            return false;
129        }
130        self.internal_annotations.contains(&span.start)
131    }
132}
133
134impl<'a> IsolatedDeclarations<'a> {
135    fn transform_program(&mut self, program: &Program<'a>) -> ArenaVec<'a, Statement<'a>> {
136        let has_import_or_export = program.body.iter().any(Statement::is_module_declaration);
137
138        if has_import_or_export {
139            self.transform_statements_on_demand(&program.body)
140        } else {
141            self.transform_program_without_module_declaration(&program.body)
142        }
143    }
144
145    fn transform_program_without_module_declaration(
146        &mut self,
147        stmts: &ArenaVec<'a, Statement<'a>>,
148    ) -> ArenaVec<'a, Statement<'a>> {
149        self.report_error_for_expando_function(stmts);
150
151        let mut stmts =
152            self.ast.vec_from_iter(stmts.iter().filter(|stmt| {
153                stmt.is_declaration() && !self.has_internal_annotation(stmt.span())
154            }));
155
156        Self::remove_function_overloads_implementation(&mut stmts);
157
158        self.ast.vec_from_iter(stmts.iter().map(|stmt| {
159            if let Some(new_decl) = self.transform_declaration(stmt.to_declaration(), false) {
160                Statement::from(new_decl)
161            } else {
162                stmt.clone_in(self.ast.allocator)
163            }
164        }))
165    }
166
167    fn transform_statements_on_demand(
168        &mut self,
169        stmts: &ArenaVec<'a, Statement<'a>>,
170    ) -> ArenaVec<'a, Statement<'a>> {
171        self.report_error_for_expando_function(stmts);
172
173        let mut stmts = self.ast.vec_from_iter(stmts.iter().filter(|stmt| {
174            (stmt.is_declaration() || stmt.is_module_declaration())
175                && !self.has_internal_annotation(stmt.span())
176        }));
177        Self::remove_function_overloads_implementation(&mut stmts);
178
179        let mut need_empty_export_marker = true;
181
182        let mut transformed_spans: FxHashSet<Span> = FxHashSet::default();
184        let mut transformed_stmts: FxHashMap<Span, Statement<'a>> = FxHashMap::default();
185        let mut transformed_variable_declarator: FxHashMap<Span, VariableDeclarator<'a>> =
186            FxHashMap::default();
187        let mut extra_export_var_statement = None;
190
191        for &stmt in &stmts {
196            match stmt {
197                match_declaration!(Statement) => {
198                    if let Statement::TSModuleDeclaration(decl) = stmt {
199                        let is_global = decl.kind.is_global();
202                        if is_global || decl.id.is_string_literal() {
203                            transformed_spans.insert(decl.span);
204
205                            let mut decl = decl.clone_in(self.ast.allocator);
206                            if !is_global {
208                                if let Some(body) =
209                                    decl.body.as_mut().and_then(|body| body.as_module_block_mut())
210                                {
211                                    self.strip_export_keyword(&mut body.body);
212                                }
213                            }
214
215                            self.scope.visit_ts_module_declaration(decl.as_ref());
217
218                            transformed_stmts.insert(
219                                decl.span,
220                                Statement::from(Declaration::TSModuleDeclaration(decl)),
221                            );
222                        }
223                    }
224                }
225                match_module_declaration!(Statement) => {
226                    match stmt.to_module_declaration() {
227                        ModuleDeclaration::TSExportAssignment(decl) => {
228                            transformed_spans.insert(decl.span);
229                            if let Some((var_decl, new_decl)) =
230                                self.transform_ts_export_assignment(decl)
231                            {
232                                if let Some(var_decl) = var_decl {
233                                    self.scope.visit_statement(&var_decl);
234                                    extra_export_var_statement = Some(var_decl);
235                                }
236
237                                self.scope.visit_statement(&new_decl);
238                                transformed_stmts.insert(decl.span, new_decl);
239                            } else {
240                                self.scope.visit_ts_export_assignment(decl);
241                            }
242                            need_empty_export_marker = false;
243                        }
244                        ModuleDeclaration::ExportDefaultDeclaration(decl) => {
245                            transformed_spans.insert(decl.span);
246                            if let Some((var_decl, new_decl)) =
247                                self.transform_export_default_declaration(decl)
248                            {
249                                if let Some(var_decl) = var_decl {
250                                    self.scope.visit_statement(&var_decl);
251                                    extra_export_var_statement = Some(var_decl);
252                                }
253
254                                self.scope.visit_statement(&new_decl);
255                                transformed_stmts.insert(decl.span, new_decl);
256                            } else {
257                                self.scope.visit_export_default_declaration(decl);
258                            }
259
260                            need_empty_export_marker = false;
261                        }
262
263                        ModuleDeclaration::ExportNamedDeclaration(decl) => {
264                            transformed_spans.insert(decl.span);
265                            if let Some(new_decl) = self.transform_export_named_declaration(decl) {
266                                self.scope.visit_export_named_declaration(&new_decl);
267                                transformed_stmts.insert(
268                                    decl.span,
269                                    Statement::from(ModuleDeclaration::ExportNamedDeclaration(
270                                        self.ast.alloc(new_decl),
271                                    )),
272                                );
273                            } else if decl.declaration.is_none() {
274                                need_empty_export_marker = false;
275                                self.scope.visit_export_named_declaration(decl);
276                            }
277                        }
278                        ModuleDeclaration::ImportDeclaration(_) => {
279                            }
281                        module_declaration => {
282                            transformed_spans.insert(module_declaration.span());
283                            self.scope.visit_module_declaration(module_declaration);
284                        }
285                    }
286                }
287                _ => {}
288            }
289        }
290
291        let last_transformed_len = transformed_spans.len() + transformed_variable_declarator.len();
292        loop {
294            let cur_transformed_len =
295                transformed_spans.len() + transformed_variable_declarator.len();
296            for stmt in &stmts {
297                if transformed_spans.contains(&stmt.span()) {
298                    continue;
299                }
300                let Some(decl) = stmt.as_declaration() else { continue };
301
302                if transformed_spans.contains(&decl.span()) {
304                    continue;
305                }
306
307                if let Declaration::VariableDeclaration(declaration) = decl {
308                    let mut all_declarator_has_transformed = true;
309                    for declarator in &declaration.declarations {
310                        if transformed_spans.contains(&declarator.span) {
311                            continue;
312                        }
313
314                        if let Some(new_declarator) =
315                            self.transform_variable_declarator(declarator, true)
316                        {
317                            self.scope.visit_variable_declarator(&new_declarator);
318                            transformed_variable_declarator.insert(declarator.span, new_declarator);
319                        } else {
320                            all_declarator_has_transformed = false;
321                        }
322                    }
323                    if all_declarator_has_transformed {
324                        let declarations = self.ast.vec_from_iter(
325                            declaration.declarations.iter().map(|declarator| {
326                                transformed_variable_declarator.remove(&declarator.span).unwrap()
327                            }),
328                        );
329                        let decl = self.ast.variable_declaration(
330                            declaration.span,
331                            declaration.kind,
332                            declarations,
333                            self.is_declare(),
334                        );
335                        transformed_stmts.insert(
336                            declaration.span,
337                            Statement::VariableDeclaration(self.ast.alloc(decl)),
338                        );
339                        transformed_spans.insert(declaration.span);
340                    }
341                } else if let Some(new_decl) = self.transform_declaration(decl, true) {
342                    self.scope.visit_declaration(&new_decl);
343                    transformed_spans.insert(new_decl.span());
344                    transformed_stmts.insert(new_decl.span(), Statement::from(new_decl));
345                }
346            }
347
348            if cur_transformed_len
350                == transformed_spans.len() + transformed_variable_declarator.len()
351            {
352                break;
353            }
354        }
355
356        if last_transformed_len != 0 && last_transformed_len == transformed_spans.len() {
358            need_empty_export_marker = false;
359        }
360
361        let mut new_stmts = self.ast.vec_with_capacity(
363            stmts.len()
364                + usize::from(extra_export_var_statement.is_some())
365                + usize::from(need_empty_export_marker),
366        );
367        stmts.iter().for_each(|stmt| {
368            if transformed_spans.contains(&stmt.span()) {
369                let new_stmt = transformed_stmts
370                    .remove(&stmt.span())
371                    .unwrap_or_else(|| stmt.clone_in(self.ast.allocator));
372                if matches!(
373                    new_stmt,
374                    Statement::ExportDefaultDeclaration(_) | Statement::TSExportAssignment(_)
375                ) {
376                    if let Some(export_external_var_statement) = extra_export_var_statement.take() {
377                        new_stmts.push(export_external_var_statement);
378                    }
379                }
380                new_stmts.push(new_stmt);
381                return;
382            }
383            match stmt {
384                Statement::ImportDeclaration(decl) => {
385                    if decl.specifiers.is_none() {
387                        new_stmts.push(stmt.clone_in(self.ast.allocator));
388                    } else if let Some(new_decl) = self.transform_import_declaration(decl) {
389                        new_stmts.push(Statement::ImportDeclaration(new_decl));
390                    }
391                }
392                Statement::VariableDeclaration(decl) => {
393                    if decl.declarations.len() > 1 {
394                        let declarations = self.ast.vec_from_iter(
396                            decl.declarations.iter().filter_map(|declarator| {
397                                transformed_variable_declarator.remove(&declarator.span)
398                            }),
399                        );
400                        new_stmts.push(Statement::VariableDeclaration(
401                            self.ast.alloc_variable_declaration(
402                                decl.span,
403                                decl.kind,
404                                declarations,
405                                self.is_declare(),
406                            ),
407                        ));
408                    }
409                }
410                _ => {}
411            }
412        });
413
414        if need_empty_export_marker {
415            let specifiers = self.ast.vec();
416            let kind = ImportOrExportKind::Value;
417            let empty_export =
418                self.ast.alloc_export_named_declaration(SPAN, None, specifiers, None, kind, NONE);
419            new_stmts
420                .push(Statement::from(ModuleDeclaration::ExportNamedDeclaration(empty_export)));
421        } else if self.scope.is_ts_module_block() {
422            self.strip_export_keyword(&mut new_stmts);
425        }
426
427        new_stmts
428    }
429
430    fn remove_function_overloads_implementation(stmts: &mut ArenaVec<'a, &Statement<'a>>) {
431        let mut last_function_name: Option<Atom<'a>> = None;
432        let mut is_export_default_function_overloads = false;
433
434        stmts.retain(move |&stmt| match stmt {
435            Statement::FunctionDeclaration(func) => {
436                let name = func
437                    .id
438                    .as_ref()
439                    .unwrap_or_else(|| {
440                        unreachable!(
441                            "Only export default function declaration is allowed to have no name"
442                        )
443                    })
444                    .name;
445
446                if func.body.is_some() {
447                    if last_function_name.as_ref().is_some_and(|&last_name| last_name == name) {
448                        return false;
449                    }
450                } else {
451                    last_function_name = Some(name);
452                }
453                true
454            }
455            Statement::ExportNamedDeclaration(decl) => {
456                if let Some(Declaration::FunctionDeclaration(func)) = &decl.declaration {
457                    let name = func
458                        .id
459                        .as_ref()
460                        .unwrap_or_else(|| {
461                            unreachable!(
462                            "Only export default function declaration is allowed to have no name"
463                        )
464                        })
465                        .name;
466                    if func.body.is_some() {
467                        if last_function_name.as_ref().is_some_and(|&last_name| last_name == name) {
468                            return false;
469                        }
470                    } else {
471                        last_function_name = Some(name);
472                    }
473                    true
474                } else {
475                    true
476                }
477            }
478            Statement::ExportDefaultDeclaration(decl) => {
479                if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &decl.declaration {
480                    if is_export_default_function_overloads && func.body.is_some() {
481                        is_export_default_function_overloads = false;
482                        return false;
483                    }
484                    is_export_default_function_overloads = true;
485                    true
486                } else {
487                    is_export_default_function_overloads = false;
488                    true
489                }
490            }
491            _ => true,
492        });
493    }
494
495    fn get_assignable_properties_for_namespaces(
496        stmts: &'a ArenaVec<'a, Statement<'a>>,
497    ) -> FxHashMap<&'a str, FxHashSet<Atom<'a>>> {
498        let mut assignable_properties_for_namespace = FxHashMap::<&str, FxHashSet<Atom>>::default();
499        for stmt in stmts {
500            let decl = match stmt {
501                Statement::ExportNamedDeclaration(decl) => {
502                    if let Some(Declaration::TSModuleDeclaration(decl)) = &decl.declaration {
503                        decl
504                    } else {
505                        continue;
506                    }
507                }
508                Statement::TSModuleDeclaration(decl) => decl,
509                _ => continue,
510            };
511
512            if decl.kind != TSModuleDeclarationKind::Namespace {
513                continue;
514            }
515            let TSModuleDeclarationName::Identifier(ident) = &decl.id else { continue };
516            let Some(TSModuleDeclarationBody::TSModuleBlock(block)) = &decl.body else {
517                continue;
518            };
519            for stmt in &block.body {
520                let Statement::ExportNamedDeclaration(decl) = stmt else { continue };
521                match &decl.declaration {
522                    Some(Declaration::VariableDeclaration(var)) => {
523                        for declarator in &var.declarations {
524                            if let Some(name) = declarator.id.get_identifier_name() {
525                                assignable_properties_for_namespace
526                                    .entry(&ident.name)
527                                    .or_default()
528                                    .insert(name);
529                            }
530                        }
531                    }
532                    Some(Declaration::FunctionDeclaration(func)) => {
533                        if let Some(name) = func.name() {
534                            assignable_properties_for_namespace
535                                .entry(&ident.name)
536                                .or_default()
537                                .insert(name);
538                        }
539                    }
540                    Some(Declaration::ClassDeclaration(cls)) => {
541                        if let Some(id) = cls.id.as_ref() {
542                            assignable_properties_for_namespace
543                                .entry(&ident.name)
544                                .or_default()
545                                .insert(id.name);
546                        }
547                    }
548                    Some(Declaration::TSEnumDeclaration(decl)) => {
549                        assignable_properties_for_namespace
550                            .entry(&ident.name)
551                            .or_default()
552                            .insert(decl.id.name);
553                    }
554                    _ => {}
555                }
556            }
557        }
558        assignable_properties_for_namespace
559    }
560
561    fn report_error_for_expando_function(&self, stmts: &ArenaVec<'a, Statement<'a>>) {
562        let assignable_properties_for_namespace =
563            IsolatedDeclarations::get_assignable_properties_for_namespaces(stmts);
564
565        let mut can_expando_function_names = FxHashSet::default();
566        for stmt in stmts {
567            match stmt {
568                Statement::ExportNamedDeclaration(decl) => match decl.declaration.as_ref() {
569                    Some(Declaration::FunctionDeclaration(func)) => {
570                        if func.body.is_some() {
571                            if let Some(id) = func.id.as_ref() {
572                                can_expando_function_names.insert(id.name);
573                            }
574                        }
575                    }
576                    Some(Declaration::VariableDeclaration(decl)) => {
577                        for declarator in &decl.declarations {
578                            if declarator.id.type_annotation.is_none()
579                                && declarator.init.as_ref().is_some_and(Expression::is_function)
580                            {
581                                if let Some(name) = declarator.id.get_identifier_name() {
582                                    can_expando_function_names.insert(name);
583                                }
584                            }
585                        }
586                    }
587                    _ => (),
588                },
589                Statement::ExportDefaultDeclaration(decl) => {
590                    if let ExportDefaultDeclarationKind::FunctionDeclaration(func) =
591                        &decl.declaration
592                    {
593                        if func.body.is_some() {
594                            if let Some(name) = func.name() {
595                                can_expando_function_names.insert(name);
596                            }
597                        }
598                    }
599                }
600                Statement::FunctionDeclaration(func) => {
601                    if func.body.is_some() {
602                        if let Some(name) = func.name() {
603                            if self.scope.has_reference(&name) {
604                                can_expando_function_names.insert(name);
605                            }
606                        }
607                    }
608                }
609                Statement::VariableDeclaration(decl) => {
610                    for declarator in &decl.declarations {
611                        if declarator.id.type_annotation.is_none()
612                            && declarator.init.as_ref().is_some_and(Expression::is_function)
613                        {
614                            if let Some(name) = declarator.id.get_identifier_name() {
615                                if self.scope.has_reference(&name) {
616                                    can_expando_function_names.insert(name);
617                                }
618                            }
619                        }
620                    }
621                }
622                Statement::ExpressionStatement(stmt) => {
623                    if let Expression::AssignmentExpression(assignment) = &stmt.expression {
624                        if let AssignmentTarget::StaticMemberExpression(static_member_expr) =
625                            &assignment.left
626                        {
627                            if let Expression::Identifier(ident) = &static_member_expr.object {
628                                if can_expando_function_names.contains(&ident.name)
629                                    && !assignable_properties_for_namespace
630                                        .get(&ident.name.as_str())
631                                        .is_some_and(|properties| {
632                                            properties.contains(&static_member_expr.property.name)
633                                        })
634                                {
635                                    self.error(function_with_assigning_properties(
636                                        static_member_expr.span,
637                                    ));
638                                }
639                            }
640                        }
641                    }
642                }
643
644                _ => {}
645            }
646        }
647    }
648
649    fn is_declare(&self) -> bool {
650        !self.scope.is_ts_module_block()
652    }
653}