Skip to main content

oxc_isolated_declarations/
declaration.rs

1use std::cell::Cell;
2
3use oxc_allocator::{Box as ArenaBox, CloneIn, Vec as ArenaVec};
4use oxc_ast::ast::*;
5use oxc_ast_visit::{Visit, VisitMut, walk_mut::walk_ts_signatures};
6use oxc_ecmascript::BoundNames;
7use oxc_span::{GetSpan, SPAN};
8use oxc_syntax::scope::ScopeFlags;
9
10use crate::{
11    IsolatedDeclarations,
12    diagnostics::{
13        accessor_must_have_explicit_return_type, binding_element_export,
14        inferred_type_of_expression, signature_computed_property_name,
15        variable_must_have_explicit_type,
16    },
17};
18
19impl<'a> IsolatedDeclarations<'a> {
20    pub(crate) fn transform_variable_declaration(
21        &self,
22        decl: &VariableDeclaration<'a>,
23        check_binding: bool,
24    ) -> Option<ArenaBox<'a, VariableDeclaration<'a>>> {
25        if decl.declare {
26            None
27        } else {
28            let declarations =
29                self.ast.vec_from_iter(decl.declarations.iter().filter_map(|declarator| {
30                    self.transform_variable_declarator(declarator, check_binding)
31                }));
32            Some(self.transform_variable_declaration_with_new_declarations(decl, declarations))
33        }
34    }
35
36    pub(crate) fn transform_variable_declaration_with_new_declarations(
37        &self,
38        decl: &VariableDeclaration<'a>,
39        declarations: ArenaVec<'a, VariableDeclarator<'a>>,
40    ) -> ArenaBox<'a, VariableDeclaration<'a>> {
41        self.ast.alloc_variable_declaration(decl.span, decl.kind, declarations, self.is_declare())
42    }
43
44    pub(crate) fn transform_variable_declarator(
45        &self,
46        decl: &VariableDeclarator<'a>,
47        check_binding: bool,
48    ) -> Option<VariableDeclarator<'a>> {
49        if decl.id.is_destructuring_pattern() {
50            decl.id.bound_names(&mut |id| {
51                if !check_binding || self.scope.has_value_reference(&id.name) {
52                    self.error(binding_element_export(id.span));
53                }
54            });
55            return None;
56        }
57
58        if check_binding
59            && let Some(name) = decl.id.get_identifier_name()
60            && !self.scope.has_value_reference(&name)
61        {
62            return None;
63        }
64
65        let mut binding_type = None;
66        let mut init = None;
67        if decl.type_annotation.is_none() {
68            if let Some(init_expr) = &decl.init {
69                // if kind is const and it doesn't need to infer type from expression
70                if decl.kind.is_const() && !Self::is_need_to_infer_type_from_expression(init_expr) {
71                    if let Expression::TemplateLiteral(lit) = init_expr {
72                        init =
73                            self.transform_template_to_string(lit).map(Expression::StringLiteral);
74                    } else {
75                        init = Some(init_expr.clone_in(self.ast.allocator));
76                    }
77                } else if !decl.kind.is_const()
78                    || !matches!(init_expr, Expression::TemplateLiteral(_))
79                {
80                    // otherwise, we need to infer type from expression
81                    binding_type = self.infer_type_from_expression(init_expr);
82                }
83            }
84            if init.is_none() && binding_type.is_none() {
85                binding_type = Some(self.ast.ts_type_unknown_keyword(SPAN));
86                if !decl.init.as_ref().is_some_and(Expression::is_function) {
87                    self.error(variable_must_have_explicit_type(decl.id.span()));
88                }
89            }
90        }
91        let id = decl.id.clone_in(self.ast.allocator);
92
93        if binding_type.is_none()
94            && let Some(ts_type) = &decl.type_annotation
95        {
96            binding_type = Some(ts_type.type_annotation.clone_in(self.ast.allocator));
97        }
98
99        let type_annotation =
100            binding_type.map(|ts_type| self.ast.ts_type_annotation(SPAN, ts_type));
101
102        Some(self.ast.variable_declarator(
103            decl.span,
104            decl.kind,
105            id,
106            type_annotation,
107            init,
108            decl.definite,
109        ))
110    }
111
112    fn transform_ts_module_block(
113        &mut self,
114        block: &ArenaBox<'a, TSModuleBlock<'a>>,
115    ) -> ArenaBox<'a, TSModuleBlock<'a>> {
116        // We need to enter a new scope for the module block, avoid add binding to the parent scope
117        // TODO: doesn't have a scope_id!
118        self.scope.enter_scope(ScopeFlags::TsModuleBlock, &Cell::default());
119        let stmts = self.transform_statements_on_demand(&block.body);
120        self.scope.leave_scope();
121        self.ast.alloc_ts_module_block(SPAN, self.ast.vec(), stmts)
122    }
123
124    pub(crate) fn transform_ts_module_declaration(
125        &mut self,
126        decl: &ArenaBox<'a, TSModuleDeclaration<'a>>,
127    ) -> ArenaBox<'a, TSModuleDeclaration<'a>> {
128        if decl.declare {
129            return decl.clone_in(self.ast.allocator);
130        }
131
132        let Some(body) = &decl.body else {
133            return decl.clone_in(self.ast.allocator);
134        };
135
136        // Follows https://github.com/microsoft/TypeScript/pull/54134
137        let kind = TSModuleDeclarationKind::Namespace;
138        match body {
139            TSModuleDeclarationBody::TSModuleDeclaration(inner_decl) => {
140                let inner = self.transform_ts_module_declaration(inner_decl);
141                self.ast.alloc_ts_module_declaration(
142                    decl.span,
143                    decl.id.clone_in(self.ast.allocator),
144                    Some(TSModuleDeclarationBody::TSModuleDeclaration(inner)),
145                    kind,
146                    self.is_declare(),
147                )
148            }
149            TSModuleDeclarationBody::TSModuleBlock(block) => {
150                let body = self.transform_ts_module_block(block);
151                self.ast.alloc_ts_module_declaration(
152                    decl.span,
153                    decl.id.clone_in(self.ast.allocator),
154                    Some(TSModuleDeclarationBody::TSModuleBlock(body)),
155                    kind,
156                    self.is_declare(),
157                )
158            }
159        }
160    }
161
162    pub(crate) fn transform_declaration(
163        &mut self,
164        decl: &Declaration<'a>,
165        check_binding: bool,
166    ) -> Option<Declaration<'a>> {
167        match decl {
168            Declaration::FunctionDeclaration(func) => {
169                let needs_transform = !check_binding
170                    || func.id.as_ref().is_some_and(|id| self.scope.has_value_reference(&id.name));
171                needs_transform
172                    .then(|| Declaration::FunctionDeclaration(self.transform_function(func, None)))
173            }
174            Declaration::VariableDeclaration(decl) => self
175                .transform_variable_declaration(decl, check_binding)
176                .map(Declaration::VariableDeclaration),
177            Declaration::ClassDeclaration(decl) => {
178                let needs_transform = !check_binding
179                    || decl.id.as_ref().is_some_and(|id| self.scope.has_reference(&id.name));
180                needs_transform
181                    .then(|| Declaration::ClassDeclaration(self.transform_class(decl, None)))
182            }
183            Declaration::TSTypeAliasDeclaration(alias_decl) => {
184                if !check_binding || self.scope.has_reference(&alias_decl.id.name) {
185                    let mut decl = decl.clone_in(self.ast.allocator);
186                    self.visit_declaration(&mut decl);
187                    Some(decl)
188                } else {
189                    None
190                }
191            }
192            Declaration::TSInterfaceDeclaration(interface_decl) => {
193                if !check_binding || self.scope.has_reference(&interface_decl.id.name) {
194                    let mut decl = decl.clone_in(self.ast.allocator);
195                    self.visit_declaration(&mut decl);
196                    Some(decl)
197                } else {
198                    None
199                }
200            }
201            Declaration::TSEnumDeclaration(enum_decl) => {
202                if !check_binding || self.scope.has_reference(&enum_decl.id.name) {
203                    Some(self.transform_ts_enum_declaration(enum_decl))
204                } else {
205                    None
206                }
207            }
208            Declaration::TSModuleDeclaration(decl) => {
209                if !check_binding
210                    || matches!(
211                        &decl.id,
212                        TSModuleDeclarationName::Identifier(ident)
213                            if self.scope.has_reference(&ident.name)
214                    )
215                {
216                    Some(Declaration::TSModuleDeclaration(
217                        self.transform_ts_module_declaration(decl),
218                    ))
219                } else {
220                    None
221                }
222            }
223            Declaration::TSGlobalDeclaration(decl) => {
224                Some(Declaration::TSGlobalDeclaration(decl.clone_in(self.ast.allocator)))
225            }
226            Declaration::TSImportEqualsDeclaration(decl) => {
227                if !check_binding || self.scope.has_reference(&decl.id.name) {
228                    Some(Declaration::TSImportEqualsDeclaration(decl.clone_in(self.ast.allocator)))
229                } else {
230                    None
231                }
232            }
233        }
234    }
235
236    fn report_signature_property_key(&self, key: &PropertyKey<'a>, computed: bool) {
237        if !computed {
238            return;
239        }
240
241        let is_not_allowed = match key {
242            PropertyKey::StaticIdentifier(_) | PropertyKey::Identifier(_) => false,
243            PropertyKey::StaticMemberExpression(expr) => {
244                !expr.get_first_object().is_identifier_reference()
245            }
246            key => !Self::is_literal_key(key),
247        };
248
249        if is_not_allowed {
250            self.error(signature_computed_property_name(key.span()));
251        }
252    }
253}
254
255impl<'a> VisitMut<'a> for IsolatedDeclarations<'a> {
256    fn visit_ts_signatures(&mut self, signatures: &mut ArenaVec<'a, TSSignature<'a>>) {
257        self.transform_ts_signatures(signatures);
258        walk_ts_signatures(self, signatures);
259    }
260
261    fn visit_ts_method_signature(&mut self, signature: &mut TSMethodSignature<'a>) {
262        self.report_signature_property_key(&signature.key, signature.computed);
263        if signature.return_type.is_none() {
264            match signature.kind {
265                TSMethodSignatureKind::Method => {
266                    self.error(inferred_type_of_expression(signature.span));
267                }
268                TSMethodSignatureKind::Get => {
269                    self.error(accessor_must_have_explicit_return_type(signature.key.span()));
270                }
271                TSMethodSignatureKind::Set => {
272                    // setter method don't need return type
273                }
274            }
275        }
276    }
277
278    fn visit_ts_property_signature(&mut self, signature: &mut TSPropertySignature<'a>) {
279        self.report_signature_property_key(&signature.key, signature.computed);
280    }
281}