1use std::{borrow::Cow, mem::take, sync::Arc};
2
3use rustc_hash::{FxHashMap, FxHashSet};
4use swc_atoms::Atom;
5use swc_common::{
6 comments::SingleThreadedComments, util::take::Take, BytePos, FileName, Mark, Span, Spanned,
7 DUMMY_SP,
8};
9use swc_ecma_ast::{
10 BindingIdent, Decl, DefaultDecl, ExportDefaultExpr, Ident, ImportSpecifier, ModuleDecl,
11 ModuleItem, NamedExport, Pat, Program, Script, Stmt, TsExportAssignment, VarDecl, VarDeclKind,
12 VarDeclarator,
13};
14use type_usage::TypeUsageAnalyzer;
15use util::{
16 ast_ext::MemberPropExt, expando_function_collector::ExpandoFunctionCollector, types::type_ann,
17};
18use visitors::type_usage::{self, SymbolFlags, UsedRefs};
19
20use crate::diagnostic::{DtsIssue, SourceRange};
21
22mod class;
23mod decl;
24mod r#enum;
25mod function;
26mod inferrer;
27mod types;
28mod util;
29mod visitors;
30
31pub struct FastDts {
41 filename: Arc<FileName>,
42 unresolved_mark: Mark,
43 diagnostics: Vec<DtsIssue>,
44 id_counter: u32,
46 is_top_level: bool,
47 used_refs: UsedRefs,
48 internal_annotations: Option<FxHashSet<BytePos>>,
49}
50
51#[derive(Debug, Default)]
52pub struct FastDtsOptions {
53 pub internal_annotations: Option<FxHashSet<BytePos>>,
54}
55
56impl FastDts {
58 pub fn new(filename: Arc<FileName>, unresolved_mark: Mark, options: FastDtsOptions) -> Self {
59 let internal_annotations = options.internal_annotations;
60 Self {
61 filename,
62 unresolved_mark,
63 diagnostics: Vec::new(),
64 id_counter: 0,
65 is_top_level: true,
66 used_refs: UsedRefs::default(),
67 internal_annotations,
68 }
69 }
70
71 pub fn mark_diagnostic<T: Into<Cow<'static, str>>>(&mut self, message: T, range: Span) {
72 self.diagnostics.push(DtsIssue {
73 message: message.into(),
74 range: SourceRange {
75 filename: self.filename.clone(),
76 span: range,
77 },
78 })
79 }
80}
81
82impl FastDts {
83 pub fn transform(&mut self, program: &mut Program) -> Vec<DtsIssue> {
84 match program {
85 Program::Module(module) => self.transform_module_body(&mut module.body, false),
86 Program::Script(script) => self.transform_script(script),
87 #[cfg(swc_ast_unknown)]
88 _ => panic!("unable to access unknown nodes"),
89 }
90 take(&mut self.diagnostics)
91 }
92
93 fn transform_module_body(
94 &mut self,
95 items: &mut Vec<ModuleItem>,
96 in_global_or_lit_module: bool,
97 ) {
98 self.used_refs.extend(TypeUsageAnalyzer::analyze(
100 items,
101 self.internal_annotations.as_ref(),
102 ));
103
104 Self::remove_function_overloads_in_module(items);
106 self.transform_module_items(items);
107
108 for item in items.iter_mut() {
110 if let Some(Stmt::Decl(Decl::TsModule(ts_module))) = item.as_mut_stmt() {
111 if ts_module.global || !ts_module.id.is_str() {
112 continue;
113 }
114
115 if let Some(body) = ts_module
116 .body
117 .as_mut()
118 .and_then(|body| body.as_mut_ts_module_block())
119 {
120 self.strip_export(&mut body.body);
121 }
122 }
123 }
124
125 self.report_error_for_expando_function_in_module(items);
127 items.retain(|item| {
128 item.as_stmt()
129 .map(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()))
130 .unwrap_or(true)
131 });
132
133 self.remove_ununsed(items, in_global_or_lit_module);
135
136 let mut has_non_exported_stmt = false;
139 let mut has_export = false;
140 for item in items.iter_mut() {
141 match item {
142 ModuleItem::Stmt(stmt) => {
143 if stmt.as_decl().map_or(true, |decl| !decl.is_ts_module()) {
144 has_non_exported_stmt = true;
145 }
146 }
147 ModuleItem::ModuleDecl(
148 ModuleDecl::ExportDefaultDecl(_)
149 | ModuleDecl::ExportDefaultExpr(_)
150 | ModuleDecl::ExportNamed(_)
151 | ModuleDecl::TsExportAssignment(_),
152 ) => has_export = true,
153 _ => {}
154 }
155 }
156 if items.is_empty() || (has_non_exported_stmt && !has_export) {
157 items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
158 NamedExport {
159 span: DUMMY_SP,
160 specifiers: Vec::new(),
161 src: None,
162 type_only: false,
163 with: None,
164 },
165 )));
166 } else if !self.is_top_level {
167 self.strip_export(items);
168 }
169 }
170
171 fn transform_script(&mut self, script: &mut Script) {
172 Self::remove_function_overloads_in_script(script);
174 let body = script.body.take();
175 for mut stmt in body {
176 if self.has_internal_annotation(stmt.span_lo()) {
177 continue;
178 }
179 if let Some(decl) = stmt.as_mut_decl() {
180 self.transform_decl(decl, false);
181 }
182 script.body.push(stmt);
183 }
184
185 self.report_error_for_expando_function_in_script(&script.body);
187 script
188 .body
189 .retain(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()));
190 }
191
192 fn transform_module_items(&mut self, items: &mut Vec<ModuleItem>) {
193 let orig_items = take(items);
194
195 for mut item in orig_items {
196 match &mut item {
197 ModuleItem::ModuleDecl(
198 ModuleDecl::Import(..)
199 | ModuleDecl::TsImportEquals(_)
200 | ModuleDecl::TsNamespaceExport(_),
201 ) => items.push(item),
202 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_) | ModuleDecl::ExportAll(_)) => {
203 items.push(item);
204 }
205 ModuleItem::Stmt(stmt) => {
206 if self.has_internal_annotation(stmt.span_lo()) {
207 continue;
208 }
209
210 if let Some(decl) = stmt.as_mut_decl() {
211 self.transform_decl(decl, true);
212 }
213 items.push(item);
214 }
215 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(expor_decl)) => {
216 if self.has_internal_annotation(expor_decl.span_lo()) {
217 continue;
218 }
219 self.transform_decl(&mut expor_decl.decl, false);
220 items.push(item);
221 }
222 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
223 self.transform_default_decl(&mut export.decl);
224 items.push(item);
225 }
226 ModuleItem::ModuleDecl(
227 ModuleDecl::ExportDefaultExpr(_) | ModuleDecl::TsExportAssignment(_),
228 ) => {
229 let expr = match &item {
230 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => {
231 &export.expr
232 }
233 ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => {
234 &export.expr
235 }
236 _ => unreachable!(),
237 };
238
239 if expr.is_ident() {
240 items.push(item);
241 continue;
242 }
243
244 let name_ident = Ident::new_no_ctxt(self.gen_unique_name("_default"), DUMMY_SP);
245 let type_ann = self.infer_type_from_expr(expr).map(type_ann);
246 self.used_refs
247 .add_usage(name_ident.to_id(), SymbolFlags::Value);
248
249 if type_ann.is_none() {
250 self.default_export_inferred(expr.span());
251 }
252
253 items.push(
254 VarDecl {
255 span: DUMMY_SP,
256 kind: VarDeclKind::Const,
257 declare: true,
258 decls: vec![VarDeclarator {
259 span: DUMMY_SP,
260 name: Pat::Ident(BindingIdent {
261 id: name_ident.clone(),
262 type_ann,
263 }),
264 init: None,
265 definite: false,
266 }],
267 ..Default::default()
268 }
269 .into(),
270 );
271
272 match &item {
273 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => items
274 .push(
275 ExportDefaultExpr {
276 span: export.span,
277 expr: name_ident.into(),
278 }
279 .into(),
280 ),
281 ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => items
282 .push(
283 TsExportAssignment {
284 span: export.span,
285 expr: name_ident.into(),
286 }
287 .into(),
288 ),
289 _ => unreachable!(),
290 };
291 }
292 #[cfg(swc_ast_unknown)]
293 _ => panic!("unable to access unknown nodes"),
294 }
295 }
296 }
297
298 fn report_error_for_expando_function_in_module(&mut self, items: &[ModuleItem]) {
299 let used_refs = self.used_refs.clone();
300 let mut assignable_properties_for_namespace = FxHashMap::<&str, FxHashSet<Atom>>::default();
301 let mut collector = ExpandoFunctionCollector::new(&used_refs);
302
303 for item in items {
304 let decl = match item {
305 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
306 if let Some(ts_module) = export_decl.decl.as_ts_module() {
307 ts_module
308 } else {
309 continue;
310 }
311 }
312 ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(ts_module))) => ts_module,
313 _ => continue,
314 };
315
316 let (Some(name), Some(block)) = (
317 decl.id.as_ident(),
318 decl.body
319 .as_ref()
320 .and_then(|body| body.as_ts_module_block()),
321 ) else {
322 continue;
323 };
324
325 for item in &block.body {
326 let Some(decl) = item.as_stmt().and_then(|stmt| stmt.as_decl()) else {
328 continue;
329 };
330
331 match &decl {
332 Decl::Class(class_decl) => {
333 assignable_properties_for_namespace
334 .entry(name.sym.as_str())
335 .or_default()
336 .insert(class_decl.ident.sym.clone());
337 }
338 Decl::Fn(fn_decl) => {
339 assignable_properties_for_namespace
340 .entry(name.sym.as_str())
341 .or_default()
342 .insert(fn_decl.ident.sym.clone());
343 }
344 Decl::Var(var_decl) => {
345 for decl in &var_decl.decls {
346 if let Some(ident) = decl.name.as_ident() {
347 assignable_properties_for_namespace
348 .entry(name.sym.as_str())
349 .or_default()
350 .insert(ident.sym.clone());
351 }
352 }
353 }
354 Decl::Using(using_decl) => {
355 for decl in &using_decl.decls {
356 if let Some(ident) = decl.name.as_ident() {
357 assignable_properties_for_namespace
358 .entry(name.sym.as_str())
359 .or_default()
360 .insert(ident.sym.clone());
361 }
362 }
363 }
364 _ => {}
365 }
366 }
367 }
368
369 for item in items {
370 match item {
371 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
372 match &export_decl.decl {
373 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
374 Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
375 _ => (),
376 }
377 }
378 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_decl)) => {
379 if let DefaultDecl::Fn(fn_expr) = &export_decl.decl {
380 collector.add_fn_expr(fn_expr)
381 }
382 }
383 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_export_named)) => {
384 }
386 ModuleItem::Stmt(Stmt::Decl(decl)) => match decl {
387 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, true),
388 Decl::Var(var_decl) => collector.add_var_decl(var_decl, true),
389 _ => (),
390 },
391 ModuleItem::Stmt(Stmt::Expr(expr_stmt)) => {
392 let Some(assign_expr) = expr_stmt.expr.as_assign() else {
393 continue;
394 };
395 let Some(member_expr) = assign_expr
396 .left
397 .as_simple()
398 .and_then(|simple| simple.as_member())
399 else {
400 continue;
401 };
402
403 if let Some(ident) = member_expr.obj.as_ident() {
404 if collector.contains(&ident.sym)
405 && !assignable_properties_for_namespace
406 .get(ident.sym.as_str())
407 .is_some_and(|properties| {
408 member_expr
409 .prop
410 .static_name()
411 .is_some_and(|name| properties.contains(name))
412 })
413 {
414 self.function_with_assigning_properties(member_expr.span);
415 }
416 }
417 }
418 _ => (),
419 }
420 }
421 }
422
423 fn report_error_for_expando_function_in_script(&mut self, stmts: &[Stmt]) {
424 let used_refs = self.used_refs.clone();
425 let mut collector = ExpandoFunctionCollector::new(&used_refs);
426 for stmt in stmts {
427 match stmt {
428 Stmt::Decl(decl) => match decl {
429 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
430 Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
431 _ => (),
432 },
433 Stmt::Expr(expr_stmt) => {
434 let Some(assign_expr) = expr_stmt.expr.as_assign() else {
435 continue;
436 };
437 let Some(member_expr) = assign_expr
438 .left
439 .as_simple()
440 .and_then(|simple| simple.as_member())
441 else {
442 continue;
443 };
444
445 if let Some(ident) = member_expr.obj.as_ident() {
446 if collector.contains(&ident.sym) {
447 self.function_with_assigning_properties(member_expr.span);
448 }
449 }
450 }
451 _ => (),
452 }
453 }
454 }
455
456 fn strip_export(&self, items: &mut Vec<ModuleItem>) {
457 for item in items {
458 if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
459 *item = ModuleItem::Stmt(Stmt::Decl(export_decl.decl.clone()));
460 }
461 }
462 }
463
464 fn remove_ununsed(&self, items: &mut Vec<ModuleItem>, in_global_or_lit_module: bool) {
465 let used_refs = &self.used_refs;
466 items.retain_mut(|node| match node {
467 ModuleItem::Stmt(Stmt::Decl(decl)) if !in_global_or_lit_module => match decl {
468 Decl::Class(class_decl) => used_refs.used(&class_decl.ident.to_id()),
469 Decl::Fn(fn_decl) => used_refs.used_as_value(&fn_decl.ident.to_id()),
470 Decl::Var(var_decl) => {
471 var_decl.decls.retain(|decl| {
472 if let Some(ident) = decl.name.as_ident() {
473 used_refs.used_as_value(&ident.to_id())
474 } else {
475 false
476 }
477 });
478 !var_decl.decls.is_empty()
479 }
480 Decl::Using(using_decl) => {
481 using_decl.decls.retain(|decl| {
482 if let Some(ident) = decl.name.as_ident() {
483 used_refs.used_as_value(&ident.to_id())
484 } else {
485 false
486 }
487 });
488 !using_decl.decls.is_empty()
489 }
490 Decl::TsInterface(ts_interface_decl) => {
491 used_refs.used_as_type(&ts_interface_decl.id.to_id())
492 }
493 Decl::TsTypeAlias(ts_type_alias_decl) => {
494 used_refs.used_as_type(&ts_type_alias_decl.id.to_id())
495 }
496 Decl::TsEnum(ts_enum) => used_refs.used(&ts_enum.id.to_id()),
497 Decl::TsModule(ts_module_decl) => {
498 ts_module_decl.global
499 || ts_module_decl.id.is_str()
500 || ts_module_decl
501 .id
502 .as_ident()
503 .map_or(true, |ident| used_refs.used_as_type(&ident.to_id()))
504 }
505 #[cfg(swc_ast_unknown)]
506 _ => panic!("unable to access unknown nodes"),
507 },
508 ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
509 if import_decl.specifiers.is_empty() {
510 return true;
511 }
512
513 import_decl.specifiers.retain(|specifier| match specifier {
514 ImportSpecifier::Named(specifier) => used_refs.used(&specifier.local.to_id()),
515 ImportSpecifier::Default(specifier) => used_refs.used(&specifier.local.to_id()),
516 ImportSpecifier::Namespace(specifier) => {
517 used_refs.used(&specifier.local.to_id())
518 }
519 #[cfg(swc_ast_unknown)]
520 _ => panic!("unable to access unknown nodes"),
521 });
522
523 !import_decl.specifiers.is_empty()
524 }
525 ModuleItem::ModuleDecl(ModuleDecl::TsImportEquals(ts_import_equals)) => {
526 used_refs.used(&ts_import_equals.id.to_id())
527 }
528 _ => true,
529 });
530 }
531
532 pub fn has_internal_annotation(&self, pos: BytePos) -> bool {
533 if let Some(internal_annotations) = &self.internal_annotations {
534 return internal_annotations.contains(&pos);
535 }
536 false
537 }
538
539 pub fn get_internal_annotations(comments: &SingleThreadedComments) -> FxHashSet<BytePos> {
540 let mut internal_annotations = FxHashSet::default();
541 let (leading, _) = comments.borrow_all();
542 for (pos, comment) in leading.iter() {
543 let has_internal_annotation = comment
544 .iter()
545 .any(|comment| comment.text.contains("@internal"));
546 if has_internal_annotation {
547 internal_annotations.insert(*pos);
548 }
549 }
550 internal_annotations
551 }
552
553 fn gen_unique_name(&mut self, name: &str) -> Atom {
554 self.id_counter += 1;
555 format!("{name}_{}", self.id_counter).into()
556 }
557}