1use anyhow::Error;
2use log::{debug, warn};
3use std::collections::hash_map::DefaultHasher;
4use std::hash::{Hash, Hasher};
5use std::{fs, path::Path, sync::Arc};
6use stylex_macros::{panic_macros::__stylex_panic, stylex_error::StyleXError, stylex_panic};
7use swc_compiler_base::{PrintArgs, SourceMapsConfig, TransformOutput, parse_js, print};
8use swc_config::is_module::IsModule;
9use swc_core::{
10 common::{
11 DUMMY_SP, EqIgnoreSpan, FileName, Mark, SourceMap, Span, Spanned, SyntaxContext,
12 errors::{Handler, *},
13 },
14 ecma::{
15 ast::*,
16 codegen::Config,
17 parser::{Syntax, TsSyntax},
18 transforms::typescript::strip,
19 visit::*,
20 },
21};
22
23use crate::shared::structures::state_manager::StateManager;
24use crate::shared::utils::ast::convertors::{
25 convert_concat_to_tpl_expr, convert_simple_tpl_to_str_expr,
26};
27use stylex_regex::regex::URL_REGEX;
28
29pub(crate) struct CodeFrame {
30 source_map: Arc<SourceMap>,
31 handler: Handler,
32}
33
34impl CodeFrame {
35 pub(crate) fn new() -> Self {
36 let source_map = Arc::new(SourceMap::default());
37 let handler =
38 Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(source_map.clone()));
39
40 Self {
41 source_map,
42 handler,
43 }
44 }
45
46 pub(crate) fn create_error<'a>(&'a self, span: Span, message: &str) -> DiagnosticBuilder<'a> {
47 let prefixed_message = format!("[StyleX] {}", message);
48 let mut diagnostic = self.handler.struct_span_err(span, &prefixed_message);
49
50 let urls = URL_REGEX
51 .find_iter(message)
52 .filter_map(|m| m.ok().map(|m| m.as_str()))
53 .collect::<Vec<_>>();
54
55 let note = format!("\n{}", urls.join("\n"));
56
57 diagnostic.warn("Line number isn't real, it's just a placeholder, Please check the actual line number in your editor.");
58
59 diagnostic.note(note.as_str());
60
61 diagnostic
62 }
63
64 pub(crate) fn get_span_line_number(&self, span: Span) -> usize {
65 self.source_map.lookup_char_pos(span.lo).line
66 }
67}
68
69fn read_source_file(file_name: &FileName) -> Result<String, std::io::Error> {
70 match file_name {
71 FileName::Real(path) => fs::read_to_string(path),
72 FileName::Custom(path) => fs::read_to_string(path),
73 FileName::Url(url) => fs::read_to_string(Path::new(url.path())),
74 _ => Err(std::io::Error::other("Unsupported file name type")),
75 }
76}
77
78pub(crate) fn build_code_frame_error<'a>(
79 wrapped_expression: &'a Expr,
80 fault_expression: &'a Expr,
81 error_message: &'a str,
82 state: &mut StateManager,
83) -> &'a str {
84 match get_span_from_source_code(wrapped_expression, fault_expression, state) {
85 Ok((code_frame, span)) => {
86 code_frame.create_error(span, error_message).emit();
87 },
88 Err(error) => {
89 if log::log_enabled!(log::Level::Debug) {
90 debug!(
91 "Failed to generate code frame error: {:?}. File: {}. Expression: {:?}.",
92 error,
93 state.get_filename(),
94 fault_expression,
95 );
96 } else {
97 warn!(
98 "Failed to generate code frame error: {:?}. File: {}. For more information enable debug logging.",
99 error,
100 state.get_filename(),
101 )
102 };
103 },
104 }
105
106 error_message
107}
108
109pub(crate) fn get_span_from_source_code(
120 wrapped_expression: &Expr,
121 target_expression: &Expr,
122 state: &mut StateManager,
123) -> Result<(CodeFrame, Span), Error> {
124 let cache_key = compute_cache_key(target_expression);
125 let file_name = FileName::Custom(state.get_filename().to_owned());
126
127 if let Some(&cached_span) = state.span_cache.get(&cache_key) {
129 let code_frame = load_code_frame_from_cache(&file_name)?;
130 return Ok((code_frame, cached_span));
131 }
132
133 let code_frame = CodeFrame::new();
134 let program = get_memoized_frame_source_code(
135 wrapped_expression,
136 target_expression,
137 state,
138 &file_name,
139 &code_frame,
140 )
141 .ok_or_else(|| anyhow::anyhow!("Failed to parse source file: {}", state.get_filename()))?;
142
143 let span = find_expression_span(program, target_expression);
144
145 state.span_cache.insert(cache_key, span);
147
148 Ok((code_frame, span))
149}
150
151fn compute_cache_key(expr: &Expr) -> u64 {
153 let mut hasher = DefaultHasher::new();
154 std::mem::discriminant(expr).hash(&mut hasher);
155 expr.hash(&mut hasher);
156 hasher.finish()
157}
158
159fn load_code_frame_from_cache(file_name: &FileName) -> Result<CodeFrame, Error> {
161 let code_frame = CodeFrame::new();
162 let source = read_source_file(file_name)
163 .map_err(|e| anyhow::anyhow!("Failed to read source file: {}", e))?;
164 code_frame
165 .source_map
166 .new_source_file(file_name.clone().into(), source);
167 Ok(code_frame)
168}
169
170fn find_expression_span(program: Program, target_expression: &Expr) -> Span {
172 let mut finder = ExpressionFinder::new(target_expression);
173 let program = program.fold_with(&mut finder);
174
175 if let Some(span) = finder.get_span() {
176 return span;
177 }
178
179 let converted_target = target_expression.clone().fold_with(&mut TplConverter {});
181 let mut fallback_finder = ExpressionFinder::new(&converted_target);
182 let _program = program.fold_with(&mut fallback_finder);
183
184 fallback_finder
185 .get_span()
186 .unwrap_or_else(|| target_expression.span())
187}
188
189fn get_memoized_frame_source_code(
192 wrapped_expression: &Expr,
193 target_expression: &Expr,
194 state: &mut StateManager,
195 file_name: &FileName,
196 code_frame: &CodeFrame,
197) -> Option<Program> {
198 if let Some((cached_program, source_code)) = state.get_seen_module_source_code()
199 && let Some(source_code) = source_code
200 {
201 code_frame
202 .source_map
203 .new_source_file(Arc::new(file_name.clone()), source_code.to_owned());
204 return Some(Program::Module(cached_program.clone()));
205 }
206
207 let source_code = get_source_code(wrapped_expression, state, file_name, code_frame)?;
208
209 let source_file = code_frame
210 .source_map
211 .new_source_file(Arc::new(file_name.clone()), source_code.clone());
212
213 let program = parse_and_normalize_program(
214 &source_file,
215 code_frame,
216 state.get_filename(),
217 target_expression,
218 )?;
219
220 state.set_seen_module_source_code(
221 match program.as_module() {
222 Some(module) => module,
223 None => stylex_panic!("Expected a module program for source code caching."),
224 },
225 Some(source_code),
226 );
227
228 Some(program)
229}
230
231fn get_source_code(
236 wrapped_expression: &Expr,
237 state: &StateManager,
238 file_name: &FileName,
239 code_frame: &CodeFrame,
240) -> Option<String> {
241 if let Some((module, source_code)) = state.get_seen_module_source_code() {
242 if let Some(source_code) = source_code {
243 return Some(source_code.clone());
244 } else {
245 return Some(print_module(
246 code_frame,
247 module.clone(),
248 Some(
249 Config::default()
250 .with_minify(false)
251 .with_omit_last_semi(false)
252 .with_reduce_escaped_newline(false)
253 .with_inline_script(false),
254 ),
255 ));
256 }
257 }
258 if let Ok(source) = read_source_file(file_name) {
259 return Some(source);
260 }
261
262 let synthetic_module = create_module(wrapped_expression);
263 Some(print_module(code_frame, synthetic_module, None))
264}
265
266fn parse_and_normalize_program(
268 source_file: &Arc<swc_core::common::SourceFile>,
269 code_frame: &CodeFrame,
270 filename: &str,
271 target_expression: &Expr,
272) -> Option<Program> {
273 let parse_result = parse_js(
274 code_frame.source_map.clone(),
275 source_file.clone(),
276 &code_frame.handler,
277 EsVersion::EsNext,
278 Syntax::Typescript(TsSyntax {
279 tsx: true,
280 ..Default::default()
281 }),
282 IsModule::Bool(true),
283 None,
284 );
285
286 match parse_result {
287 Ok(program) => {
288 let unresolved_mark = Mark::new();
289 let top_level_mark = Mark::new();
290
291 let normalized = program
293 .apply(strip(unresolved_mark, top_level_mark))
294 .fold_with(&mut TplConverter {});
295 Some(normalized)
296 },
297 Err(error) => {
298 if log::log_enabled!(log::Level::Debug) {
299 debug!(
300 "Failed to parse program: {:?}. File: {}. Expression: {:?}",
301 error, filename, target_expression
302 );
303 } else {
304 warn!("Failed to parse program: {:?}. File: {}", error, filename);
305 }
306 None
307 },
308 }
309}
310
311pub(crate) fn print_module(
312 code_frame: &CodeFrame,
313 module: Module,
314 codegen_config: Option<Config>,
315) -> String {
316 print_program(code_frame, Program::Module(module), codegen_config)
317}
318
319pub(crate) fn print_program(
320 code_frame: &CodeFrame,
321 program: Program,
322 codegen_config: Option<Config>,
323) -> String {
324 let printed_source_code = print(
325 code_frame.source_map.clone(),
326 &program,
327 PrintArgs {
328 source_map: SourceMapsConfig::Bool(false),
329 codegen_config: codegen_config.unwrap_or_default(),
330 ..Default::default()
331 },
332 )
333 .unwrap_or_else(|_| TransformOutput {
334 code: "".to_string(),
335 map: None,
336 output: None,
337 diagnostics: Vec::default(),
338 });
339
340 printed_source_code.code
341}
342
343pub(crate) fn create_module(wrapped_expression: &Expr) -> Module {
344 Module {
345 span: DUMMY_SP,
346 body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
347 span: DUMMY_SP,
348 expr: Box::new(wrapped_expression.clone()),
349 }))],
350 shebang: None,
351 }
352}
353
354#[derive(Debug)]
357struct ExpressionFinder {
358 target: Expr,
359 target_discriminant: std::mem::Discriminant<Expr>,
360 found_expr: Option<Expr>,
361}
362
363#[derive(Debug)]
366struct Cleaner {}
367impl Fold for Cleaner {
368 noop_fold_type!();
369
370 fn fold_binding_ident(&mut self, mut node: BindingIdent) -> BindingIdent {
371 node.id.ctxt = SyntaxContext::empty();
372 node.type_ann = None;
373 node.fold_children_with(self)
374 }
375
376 fn fold_ident(&mut self, mut ident: Ident) -> Ident {
377 ident.ctxt = SyntaxContext::empty();
378 ident.fold_children_with(self)
379 }
380}
381
382impl ExpressionFinder {
383 fn new(target: &Expr) -> Self {
384 let cleaned_target = target.clone().fold_children_with(&mut Cleaner {});
385 let target_discriminant = std::mem::discriminant(&cleaned_target);
386
387 Self {
388 target: cleaned_target,
389 target_discriminant,
390 found_expr: None,
391 }
392 }
393
394 fn get_span(&self) -> Option<Span> {
395 let expr = self.found_expr.as_ref()?;
396
397 Some(Span::new(expr.span_lo(), expr.span_hi()))
398 }
399}
400
401#[derive(Debug)]
404struct TplConverter {}
405
406impl Fold for TplConverter {
407 noop_fold_type!();
408
409 fn fold_expr(&mut self, expr: Expr) -> Expr {
410 let expr = convert_concat_to_tpl_expr(expr);
411 let expr = convert_simple_tpl_to_str_expr(expr);
412 expr.fold_children_with(self)
413 }
414}
415
416impl Fold for ExpressionFinder {
417 noop_fold_type!();
418
419 fn fold_expr(&mut self, expr: Expr) -> Expr {
420 if self.found_expr.is_some() {
421 return expr;
422 }
423
424 if std::mem::discriminant(&expr) != self.target_discriminant {
426 return expr.fold_children_with(self);
427 }
428
429 if self.target.eq_ignore_span(&expr) {
431 self.found_expr = Some(expr.clone());
432 return expr;
433 }
434
435 expr.fold_children_with(self)
436 }
437}
438
439#[track_caller]
440pub(crate) fn build_code_frame_error_and_panic(
441 wrapped_expression: &Expr,
442 fault_expression: &Expr,
443 error_message: &str,
444 state: &mut StateManager,
445) -> ! {
446 let caller_location = std::panic::Location::caller();
447
448 let (file, line) = match get_span_from_source_code(wrapped_expression, fault_expression, state) {
450 Ok((code_frame, span)) => {
451 code_frame.create_error(span, error_message).emit();
452 let line_num = code_frame.get_span_line_number(span);
453 (Some(state.get_filename().to_owned()), Some(line_num))
454 },
455 Err(error) => {
456 if log::log_enabled!(log::Level::Debug) {
457 debug!(
458 "Failed to generate code frame error: {:?}. File: {}. Expression: {:?}.",
459 error,
460 state.get_filename(),
461 fault_expression,
462 );
463 } else {
464 warn!(
465 "Failed to generate code frame error: {:?}. File: {}. For more information enable debug logging.",
466 error,
467 state.get_filename(),
468 );
469 }
470 (Some(state.get_filename().to_owned()), None)
471 },
472 };
473
474 let err = StyleXError {
475 message: error_message.to_string(),
476 file,
477 key_path: None,
478 line,
479 col: None,
480 source_location: Some(format!(
481 "{}:{}",
482 caller_location.file(),
483 caller_location.line()
484 )),
485 };
486
487 __stylex_panic(err)
488}