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
11#[derive(Debug)]
12pub struct DebugInstantStack {
13 page_or_layout: Regex,
14}
15
16impl DebugInstantStack {
17 pub fn new<I, S>(page_extensions: I) -> Self
18 where
19 I: IntoIterator<Item = S>,
20 S: AsRef<str>,
21 {
22 let mut result = String::from(r"[\\/](page|layout|default)\.");
23 let mut iter = page_extensions.into_iter();
24 if let Some(first) = iter.next() {
25 result.push('(');
26 result.push_str(®ex::escape(first.as_ref()));
27 for ext in iter {
28 result.push('|');
29 result.push_str(®ex::escape(ext.as_ref()));
30 }
31 result.push(')');
32 } else {
33 result.push_str("(ts|js)x?");
34 }
35 result.push('$');
36 Self {
37 page_or_layout: Regex::new(&result).unwrap(),
38 }
39 }
40 pub fn get_pass(&self, filepath: String) -> impl Pass + use<> {
41 visit_mut_pass(DebugInstantStackPass {
42 filepath,
43 instant_export_span: None,
44 page_or_layout: self.page_or_layout.clone(),
45 })
46 }
47}
48
49struct DebugInstantStackPass {
50 filepath: String,
51 instant_export_span: Option<Span>,
52 page_or_layout: Regex,
53}
54
55fn get_instant_specifier_names(specifier: &ExportSpecifier) -> Option<(&Ident, &Ident)> {
58 match specifier {
59 ExportSpecifier::Named(ExportNamedSpecifier {
61 exported: Some(ModuleExportName::Ident(exported)),
62 orig: ModuleExportName::Ident(orig),
63 ..
64 }) if exported.sym == "instant" => Some((exported, orig)),
65 ExportSpecifier::Named(ExportNamedSpecifier {
67 exported: None,
68 orig: ModuleExportName::Ident(orig),
69 ..
70 }) if orig.sym == "instant" => Some((orig, orig)),
71 _ => None,
72 }
73}
74
75fn find_var_init_span(items: &[ModuleItem], local_name: &str) -> Option<Span> {
77 for item in items {
78 let decl = match item {
79 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => var_decl,
80 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
81 if let Decl::Var(var_decl) = &export_decl.decl {
82 var_decl
83 } else {
84 continue;
85 }
86 }
87 _ => continue,
88 };
89 for d in &decl.decls {
90 if let Pat::Ident(ident) = &d.name
91 && ident.id.sym == local_name
92 && let Some(init) = &d.init
93 {
94 return Some(init.span());
95 }
96 }
97 }
98 None
99}
100
101impl VisitMut for DebugInstantStackPass {
102 fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
103 if !self.page_or_layout.is_match(&self.filepath) {
104 return;
105 }
106
107 for item in items.iter() {
108 match item {
109 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
111 if let Decl::Var(var_decl) = &export_decl.decl {
112 for decl in &var_decl.decls {
113 if let Pat::Ident(ident) = &decl.name
114 && ident.id.sym == "instant"
115 && let Some(init) = &decl.init
116 {
117 self.instant_export_span = Some(init.span());
118 }
119 }
120 }
121 }
122 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
125 for specifier in &named.specifiers {
126 if let Some((_exported, orig)) = get_instant_specifier_names(specifier) {
127 if named.src.is_some() {
128 self.instant_export_span = Some(specifier.span());
131 } else {
132 let local_name = &orig.sym;
134 if let Some(init_span) = find_var_init_span(items, local_name) {
135 self.instant_export_span = Some(init_span);
136 } else {
137 self.instant_export_span = Some(specifier.span());
139 }
140 }
141 }
142 }
143 }
144 _ => {}
145 }
146 }
147
148 if let Some(source_span) = self.instant_export_span {
149 let mut new_error = quote!("new Error(' ')" as Expr);
154 if let Expr::New(new_expr) = &mut new_error {
155 new_expr.span = source_span;
156 }
157
158 let mut cons = quote!(
159 "function instant() {
160 const error = $new_error
161 error.name = 'Instant Validation'
162 return error
163 }" as Expr,
164 new_error: Expr = new_error,
165 );
166
167 if let Expr::Fn(f) = &mut cons {
170 f.function.span = source_span;
171 }
172
173 let export = quote!(
174 "export const __debugCreateInstantConfigStack =
175 process.env.NODE_ENV !== 'production' ? $cons : null"
176 as ModuleItem,
177 cons: Expr = cons,
178 );
179
180 items.push(export);
181 }
182 }
183}