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 }
88 take(&mut self.diagnostics)
89 }
90
91 fn transform_module_body(
92 &mut self,
93 items: &mut Vec<ModuleItem>,
94 in_global_or_lit_module: bool,
95 ) {
96 self.used_refs.extend(TypeUsageAnalyzer::analyze(
98 items,
99 self.internal_annotations.as_ref(),
100 ));
101
102 Self::remove_function_overloads_in_module(items);
104 self.transform_module_items(items);
105
106 for item in items.iter_mut() {
108 if let Some(Stmt::Decl(Decl::TsModule(ts_module))) = item.as_mut_stmt() {
109 if ts_module.global || !ts_module.id.is_str() {
110 continue;
111 }
112
113 if let Some(body) = ts_module
114 .body
115 .as_mut()
116 .and_then(|body| body.as_mut_ts_module_block())
117 {
118 self.strip_export(&mut body.body);
119 }
120 }
121 }
122
123 self.report_error_for_expando_function_in_module(items);
125 items.retain(|item| {
126 item.as_stmt()
127 .map(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()))
128 .unwrap_or(true)
129 });
130
131 self.remove_ununsed(items, in_global_or_lit_module);
133
134 let mut has_non_exported_stmt = false;
137 let mut has_export = false;
138 for item in items.iter_mut() {
139 match item {
140 ModuleItem::Stmt(stmt) => {
141 if stmt.as_decl().map_or(true, |decl| !decl.is_ts_module()) {
142 has_non_exported_stmt = true;
143 }
144 }
145 ModuleItem::ModuleDecl(
146 ModuleDecl::ExportDefaultDecl(_)
147 | ModuleDecl::ExportDefaultExpr(_)
148 | ModuleDecl::ExportNamed(_)
149 | ModuleDecl::TsExportAssignment(_),
150 ) => has_export = true,
151 _ => {}
152 }
153 }
154 if items.is_empty() || (has_non_exported_stmt && !has_export) {
155 items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
156 NamedExport {
157 span: DUMMY_SP,
158 specifiers: Vec::new(),
159 src: None,
160 type_only: false,
161 with: None,
162 },
163 )));
164 } else if !self.is_top_level {
165 self.strip_export(items);
166 }
167 }
168
169 fn transform_script(&mut self, script: &mut Script) {
170 Self::remove_function_overloads_in_script(script);
172 let body = script.body.take();
173 for mut stmt in body {
174 if self.has_internal_annotation(stmt.span_lo()) {
175 continue;
176 }
177 if let Some(decl) = stmt.as_mut_decl() {
178 self.transform_decl(decl, false);
179 }
180 script.body.push(stmt);
181 }
182
183 self.report_error_for_expando_function_in_script(&script.body);
185 script
186 .body
187 .retain(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()));
188 }
189
190 fn transform_module_items(&mut self, items: &mut Vec<ModuleItem>) {
191 let orig_items = take(items);
192
193 for mut item in orig_items {
194 match &mut item {
195 ModuleItem::ModuleDecl(
196 ModuleDecl::Import(..)
197 | ModuleDecl::TsImportEquals(_)
198 | ModuleDecl::TsNamespaceExport(_),
199 ) => items.push(item),
200 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_) | ModuleDecl::ExportAll(_)) => {
201 items.push(item);
202 }
203 ModuleItem::Stmt(stmt) => {
204 if self.has_internal_annotation(stmt.span_lo()) {
205 continue;
206 }
207
208 if let Some(decl) = stmt.as_mut_decl() {
209 self.transform_decl(decl, true);
210 }
211 items.push(item);
212 }
213 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(expor_decl)) => {
214 if self.has_internal_annotation(expor_decl.span_lo()) {
215 continue;
216 }
217 self.transform_decl(&mut expor_decl.decl, false);
218 items.push(item);
219 }
220 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
221 self.transform_default_decl(&mut export.decl);
222 items.push(item);
223 }
224 ModuleItem::ModuleDecl(
225 ModuleDecl::ExportDefaultExpr(_) | ModuleDecl::TsExportAssignment(_),
226 ) => {
227 let expr = match &item {
228 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => {
229 &export.expr
230 }
231 ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => {
232 &export.expr
233 }
234 _ => unreachable!(),
235 };
236
237 if expr.is_ident() {
238 items.push(item);
239 continue;
240 }
241
242 let name_ident = Ident::new_no_ctxt(self.gen_unique_name("_default"), DUMMY_SP);
243 let type_ann = self.infer_type_from_expr(expr).map(type_ann);
244 self.used_refs
245 .add_usage(name_ident.to_id(), SymbolFlags::Value);
246
247 if type_ann.is_none() {
248 self.default_export_inferred(expr.span());
249 }
250
251 items.push(
252 VarDecl {
253 span: DUMMY_SP,
254 kind: VarDeclKind::Const,
255 declare: true,
256 decls: vec![VarDeclarator {
257 span: DUMMY_SP,
258 name: Pat::Ident(BindingIdent {
259 id: name_ident.clone(),
260 type_ann,
261 }),
262 init: None,
263 definite: false,
264 }],
265 ..Default::default()
266 }
267 .into(),
268 );
269
270 match &item {
271 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => items
272 .push(
273 ExportDefaultExpr {
274 span: export.span,
275 expr: name_ident.into(),
276 }
277 .into(),
278 ),
279 ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => items
280 .push(
281 TsExportAssignment {
282 span: export.span,
283 expr: name_ident.into(),
284 }
285 .into(),
286 ),
287 _ => unreachable!(),
288 };
289 }
290 }
291 }
292 }
293
294 fn report_error_for_expando_function_in_module(&mut self, items: &[ModuleItem]) {
295 let used_refs = self.used_refs.clone();
296 let mut assignable_properties_for_namespace = FxHashMap::<&str, FxHashSet<Atom>>::default();
297 let mut collector = ExpandoFunctionCollector::new(&used_refs);
298
299 for item in items {
300 let decl = match item {
301 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
302 if let Some(ts_module) = export_decl.decl.as_ts_module() {
303 ts_module
304 } else {
305 continue;
306 }
307 }
308 ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(ts_module))) => ts_module,
309 _ => continue,
310 };
311
312 let (Some(name), Some(block)) = (
313 decl.id.as_ident(),
314 decl.body
315 .as_ref()
316 .and_then(|body| body.as_ts_module_block()),
317 ) else {
318 continue;
319 };
320
321 for item in &block.body {
322 let Some(decl) = item.as_stmt().and_then(|stmt| stmt.as_decl()) else {
324 continue;
325 };
326
327 match &decl {
328 Decl::Class(class_decl) => {
329 assignable_properties_for_namespace
330 .entry(name.sym.as_str())
331 .or_default()
332 .insert(class_decl.ident.sym.clone());
333 }
334 Decl::Fn(fn_decl) => {
335 assignable_properties_for_namespace
336 .entry(name.sym.as_str())
337 .or_default()
338 .insert(fn_decl.ident.sym.clone());
339 }
340 Decl::Var(var_decl) => {
341 for decl in &var_decl.decls {
342 if let Some(ident) = decl.name.as_ident() {
343 assignable_properties_for_namespace
344 .entry(name.sym.as_str())
345 .or_default()
346 .insert(ident.sym.clone());
347 }
348 }
349 }
350 Decl::Using(using_decl) => {
351 for decl in &using_decl.decls {
352 if let Some(ident) = decl.name.as_ident() {
353 assignable_properties_for_namespace
354 .entry(name.sym.as_str())
355 .or_default()
356 .insert(ident.sym.clone());
357 }
358 }
359 }
360 _ => {}
361 }
362 }
363 }
364
365 for item in items {
366 match item {
367 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
368 match &export_decl.decl {
369 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
370 Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
371 _ => (),
372 }
373 }
374 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_decl)) => {
375 if let DefaultDecl::Fn(fn_expr) = &export_decl.decl {
376 collector.add_fn_expr(fn_expr)
377 }
378 }
379 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_export_named)) => {
380 }
382 ModuleItem::Stmt(Stmt::Decl(decl)) => match decl {
383 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, true),
384 Decl::Var(var_decl) => collector.add_var_decl(var_decl, true),
385 _ => (),
386 },
387 ModuleItem::Stmt(Stmt::Expr(expr_stmt)) => {
388 let Some(assign_expr) = expr_stmt.expr.as_assign() else {
389 continue;
390 };
391 let Some(member_expr) = assign_expr
392 .left
393 .as_simple()
394 .and_then(|simple| simple.as_member())
395 else {
396 continue;
397 };
398
399 if let Some(ident) = member_expr.obj.as_ident() {
400 if collector.contains(&ident.sym)
401 && !assignable_properties_for_namespace
402 .get(ident.sym.as_str())
403 .is_some_and(|properties| {
404 member_expr
405 .prop
406 .static_name()
407 .is_some_and(|name| properties.contains(name))
408 })
409 {
410 self.function_with_assigning_properties(member_expr.span);
411 }
412 }
413 }
414 _ => (),
415 }
416 }
417 }
418
419 fn report_error_for_expando_function_in_script(&mut self, stmts: &[Stmt]) {
420 let used_refs = self.used_refs.clone();
421 let mut collector = ExpandoFunctionCollector::new(&used_refs);
422 for stmt in stmts {
423 match stmt {
424 Stmt::Decl(decl) => match decl {
425 Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
426 Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
427 _ => (),
428 },
429 Stmt::Expr(expr_stmt) => {
430 let Some(assign_expr) = expr_stmt.expr.as_assign() else {
431 continue;
432 };
433 let Some(member_expr) = assign_expr
434 .left
435 .as_simple()
436 .and_then(|simple| simple.as_member())
437 else {
438 continue;
439 };
440
441 if let Some(ident) = member_expr.obj.as_ident() {
442 if collector.contains(&ident.sym) {
443 self.function_with_assigning_properties(member_expr.span);
444 }
445 }
446 }
447 _ => (),
448 }
449 }
450 }
451
452 fn strip_export(&self, items: &mut Vec<ModuleItem>) {
453 for item in items {
454 if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
455 *item = ModuleItem::Stmt(Stmt::Decl(export_decl.decl.clone()));
456 }
457 }
458 }
459
460 fn remove_ununsed(&self, items: &mut Vec<ModuleItem>, in_global_or_lit_module: bool) {
461 let used_refs = &self.used_refs;
462 items.retain_mut(|node| match node {
463 ModuleItem::Stmt(Stmt::Decl(decl)) if !in_global_or_lit_module => match decl {
464 Decl::Class(class_decl) => used_refs.used(&class_decl.ident.to_id()),
465 Decl::Fn(fn_decl) => used_refs.used_as_value(&fn_decl.ident.to_id()),
466 Decl::Var(var_decl) => {
467 var_decl.decls.retain(|decl| {
468 if let Some(ident) = decl.name.as_ident() {
469 used_refs.used_as_value(&ident.to_id())
470 } else {
471 false
472 }
473 });
474 !var_decl.decls.is_empty()
475 }
476 Decl::Using(using_decl) => {
477 using_decl.decls.retain(|decl| {
478 if let Some(ident) = decl.name.as_ident() {
479 used_refs.used_as_value(&ident.to_id())
480 } else {
481 false
482 }
483 });
484 !using_decl.decls.is_empty()
485 }
486 Decl::TsInterface(ts_interface_decl) => {
487 used_refs.used_as_type(&ts_interface_decl.id.to_id())
488 }
489 Decl::TsTypeAlias(ts_type_alias_decl) => {
490 used_refs.used_as_type(&ts_type_alias_decl.id.to_id())
491 }
492 Decl::TsEnum(ts_enum) => used_refs.used(&ts_enum.id.to_id()),
493 Decl::TsModule(ts_module_decl) => {
494 ts_module_decl.global
495 || ts_module_decl.id.is_str()
496 || ts_module_decl
497 .id
498 .as_ident()
499 .map_or(true, |ident| used_refs.used_as_type(&ident.to_id()))
500 }
501 },
502 ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
503 if import_decl.specifiers.is_empty() {
504 return true;
505 }
506
507 import_decl.specifiers.retain(|specifier| match specifier {
508 ImportSpecifier::Named(specifier) => used_refs.used(&specifier.local.to_id()),
509 ImportSpecifier::Default(specifier) => used_refs.used(&specifier.local.to_id()),
510 ImportSpecifier::Namespace(specifier) => {
511 used_refs.used(&specifier.local.to_id())
512 }
513 });
514
515 !import_decl.specifiers.is_empty()
516 }
517 ModuleItem::ModuleDecl(ModuleDecl::TsImportEquals(ts_import_equals)) => {
518 used_refs.used(&ts_import_equals.id.to_id())
519 }
520 _ => true,
521 });
522 }
523
524 pub fn has_internal_annotation(&self, pos: BytePos) -> bool {
525 if let Some(internal_annotations) = &self.internal_annotations {
526 return internal_annotations.contains(&pos);
527 }
528 false
529 }
530
531 pub fn get_internal_annotations(comments: &SingleThreadedComments) -> FxHashSet<BytePos> {
532 let mut internal_annotations = FxHashSet::default();
533 let (leading, _) = comments.borrow_all();
534 for (pos, comment) in leading.iter() {
535 let has_internal_annotation = comment
536 .iter()
537 .any(|comment| comment.text.contains("@internal"));
538 if has_internal_annotation {
539 internal_annotations.insert(*pos);
540 }
541 }
542 internal_annotations
543 }
544
545 fn gen_unique_name(&mut self, name: &str) -> Atom {
546 self.id_counter += 1;
547 format!("{name}_{}", self.id_counter).into()
548 }
549}