next_custom_transforms/transforms/
debug_instant_stack.rs1use regex::Regex;
2use swc_core::{
3 common::{Span, Spanned},
4 ecma::{
5 ast::*,
6 visit::{VisitMut, visit_mut_pass},
7 },
8 quote,
9};
10
11fn build_page_extensions_regex(page_extensions: &[String]) -> String {
12 if page_extensions.is_empty() {
13 "(ts|js)x?".to_string()
14 } else {
15 let escaped: Vec<String> = page_extensions
16 .iter()
17 .map(|ext| regex::escape(ext))
18 .collect();
19 format!("({})", escaped.join("|"))
20 }
21}
22
23pub fn debug_instant_stack(filepath: String, page_extensions: Vec<String>) -> impl Pass {
24 visit_mut_pass(DebugInstantStack {
25 filepath,
26 instant_export_span: None,
27 page_extensions,
28 })
29}
30
31struct DebugInstantStack {
32 filepath: String,
33 instant_export_span: Option<Span>,
34 page_extensions: Vec<String>,
35}
36
37fn get_instant_specifier_names(specifier: &ExportSpecifier) -> Option<(&Ident, &Ident)> {
40 match specifier {
41 ExportSpecifier::Named(ExportNamedSpecifier {
43 exported: Some(ModuleExportName::Ident(exported)),
44 orig: ModuleExportName::Ident(orig),
45 ..
46 }) if exported.sym == "unstable_instant" => Some((exported, orig)),
47 ExportSpecifier::Named(ExportNamedSpecifier {
49 exported: None,
50 orig: ModuleExportName::Ident(orig),
51 ..
52 }) if orig.sym == "unstable_instant" => Some((orig, orig)),
53 _ => None,
54 }
55}
56
57fn find_var_init_span(items: &[ModuleItem], local_name: &str) -> Option<Span> {
59 for item in items {
60 let decl = match item {
61 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => var_decl,
62 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
63 if let Decl::Var(var_decl) = &export_decl.decl {
64 var_decl
65 } else {
66 continue;
67 }
68 }
69 _ => continue,
70 };
71 for d in &decl.decls {
72 if let Pat::Ident(ident) = &d.name
73 && ident.id.sym == local_name
74 && let Some(init) = &d.init
75 {
76 return Some(init.span());
77 }
78 }
79 }
80 None
81}
82
83impl VisitMut for DebugInstantStack {
84 fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
85 let ext_pattern = build_page_extensions_regex(&self.page_extensions);
86 let page_or_layout_re =
87 Regex::new(&format!(r"[\\/](page|layout|default)\.{ext_pattern}$")).unwrap();
88 if !page_or_layout_re.is_match(&self.filepath) {
89 return;
90 }
91
92 for item in items.iter() {
93 match item {
94 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
96 if let Decl::Var(var_decl) = &export_decl.decl {
97 for decl in &var_decl.decls {
98 if let Pat::Ident(ident) = &decl.name
99 && ident.id.sym == "unstable_instant"
100 && let Some(init) = &decl.init
101 {
102 self.instant_export_span = Some(init.span());
103 }
104 }
105 }
106 }
107 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
110 for specifier in &named.specifiers {
111 if let Some((_exported, orig)) = get_instant_specifier_names(specifier) {
112 if named.src.is_some() {
113 self.instant_export_span = Some(specifier.span());
116 } else {
117 let local_name = &orig.sym;
119 if let Some(init_span) = find_var_init_span(items, local_name) {
120 self.instant_export_span = Some(init_span);
121 } else {
122 self.instant_export_span = Some(specifier.span());
124 }
125 }
126 }
127 }
128 }
129 _ => {}
130 }
131 }
132
133 if let Some(source_span) = self.instant_export_span {
134 let mut new_error = quote!("new Error(' ')" as Expr);
139 if let Expr::New(new_expr) = &mut new_error {
140 new_expr.span = source_span;
141 }
142
143 let mut cons = quote!(
144 "function unstable_instant() {
145 const error = $new_error
146 error.name = 'Instant Validation'
147 return error
148 }" as Expr,
149 new_error: Expr = new_error,
150 );
151
152 if let Expr::Fn(f) = &mut cons {
155 f.function.span = source_span;
156 }
157
158 let export = quote!(
159 "export const __debugCreateInstantConfigStack =
160 process.env.NODE_ENV !== 'production' ? $cons : null"
161 as ModuleItem,
162 cons: Expr = cons,
163 );
164
165 items.push(export);
166 }
167 }
168}