swc_error_reporters/
handler.rs

1use std::{
2    env,
3    fmt::Debug,
4    mem::take,
5    sync::{Arc, Mutex},
6};
7
8use miette::{GraphicalReportHandler, GraphicalTheme};
9use once_cell::sync::Lazy;
10use swc_common::{
11    errors::{ColorConfig, Diagnostic, Emitter, Handler, HANDLER},
12    sync::Lrc,
13    SourceMap,
14};
15
16use crate::{diagnostic::ToPrettyDiagnostic, ErrorEmitter, TWithDiagnosticArray};
17
18#[derive(Default, Clone)]
19/// A thread-safe container for managing a collection of diagnostics.
20pub struct ThreadSafetyDiagnostics(Arc<Mutex<Vec<Diagnostic>>>);
21
22impl ThreadSafetyDiagnostics {
23    /// Adds a new diagnostic to the collection.
24    pub fn push(&mut self, d: Diagnostic) {
25        self.0
26            .lock()
27            .expect("Failed to access the diagnostics lock")
28            .push(d);
29    }
30
31    /// Removes and returns all diagnostics from the collection.
32    pub fn take(&mut self) -> Vec<Diagnostic> {
33        take(
34            &mut *self
35                .0
36                .lock()
37                .expect("Failed to access the diagnostics lock"),
38        )
39    }
40
41    pub fn as_array(
42        mut self,
43        cm: Lrc<SourceMap>,
44        opts: HandlerOpts,
45    ) -> TWithDiagnosticArray<anyhow::Error> {
46        let diagnostics = self.take();
47
48        TWithDiagnosticArray {
49            inner: None,
50            diagnostics,
51            cm,
52            report_opts: opts,
53        }
54    }
55
56    pub fn as_array_with_anyhow(
57        mut self,
58        inner: anyhow::Error,
59        cm: Lrc<SourceMap>,
60        opts: HandlerOpts,
61    ) -> TWithDiagnosticArray<anyhow::Error> {
62        let diagnostics = self.take();
63
64        TWithDiagnosticArray {
65            inner: Some(inner),
66            diagnostics,
67            cm,
68            report_opts: opts,
69        }
70    }
71
72    /// Returns a vector of diagnostics in the collection.
73    ///
74    /// # Panics
75    /// This method locks the mutex to ensure thread safety and returns a vector
76    /// of diagnostics. If the lock cannot be acquired, it will panic with the
77    /// message "Failed to access the diagnostics lock".
78    ///
79    /// # Arguments
80    ///
81    /// * `cm` - The source map.
82    /// * `skip_filename` - Whether to skip the filename in the output.
83    /// * `color` - The color configuration for the output.
84    pub fn to_pretty_string(
85        &self,
86        cm: &SourceMap,
87        skip_filename: bool,
88        color: ColorConfig,
89    ) -> Vec<String> {
90        let handler = to_pretty_handler(color);
91        self.0
92            .lock()
93            .expect("Failed to access the diagnostics lock")
94            .iter()
95            .map(|d| d.to_pretty_string(cm, skip_filename, &handler))
96            .collect::<Vec<String>>()
97    }
98
99    /// Determines if the diagnostics collection includes any errors.
100    ///
101    /// Iterates through the diagnostics to find any with a level of `Error`,
102    /// returning `true` if found.
103    pub fn contains_error(&self) -> bool {
104        self.0
105            .lock()
106            .expect("Failed to access the diagnostics lock")
107            .iter()
108            .any(|d| d.level == swc_common::errors::Level::Error)
109    }
110}
111
112#[derive(Debug, Clone, Copy)]
113pub struct HandlerOpts {
114    /// [ColorConfig::Auto] is the default, and it will print colors unless the
115    /// environment variable `NO_COLOR` is not 1.
116    pub color: ColorConfig,
117
118    /// Defaults to `false`.
119    pub skip_filename: bool,
120}
121
122impl Default for HandlerOpts {
123    fn default() -> Self {
124        Self {
125            color: ColorConfig::Auto,
126            skip_filename: false,
127        }
128    }
129}
130
131pub fn to_pretty_handler(color: ColorConfig) -> GraphicalReportHandler {
132    match color {
133        ColorConfig::Auto => {
134            if cfg!(target_arch = "wasm32") {
135                return to_pretty_handler(ColorConfig::Always).with_context_lines(3);
136            }
137
138            static ENABLE: Lazy<bool> =
139                Lazy::new(|| !env::var("NO_COLOR").map(|s| s == "1").unwrap_or(false));
140
141            if *ENABLE {
142                to_pretty_handler(ColorConfig::Always)
143            } else {
144                to_pretty_handler(ColorConfig::Never)
145            }
146        }
147        ColorConfig::Always => GraphicalReportHandler::default(),
148        ColorConfig::Never => GraphicalReportHandler::default().with_theme(GraphicalTheme::none()),
149    }
150    .with_context_lines(3)
151    .with_wrap_lines(false)
152    .with_break_words(false)
153}
154
155/// Try operation with a [Handler] and prints the errors as a [String] wrapped
156/// by [Err].
157pub fn try_with_handler<F, Ret>(
158    cm: Lrc<SourceMap>,
159    config: HandlerOpts,
160    op: F,
161) -> Result<Ret, TWithDiagnosticArray<anyhow::Error>>
162where
163    F: FnOnce(&Handler) -> Result<Ret, anyhow::Error>,
164{
165    try_with_handler_inner(cm, config, op)
166}
167
168fn try_with_handler_inner<F, Ret>(
169    cm: Lrc<SourceMap>,
170    config: HandlerOpts,
171    op: F,
172) -> Result<Ret, TWithDiagnosticArray<anyhow::Error>>
173where
174    F: FnOnce(&Handler) -> Result<Ret, anyhow::Error>,
175{
176    let diagnostics = ThreadSafetyDiagnostics::default();
177
178    let emitter: Box<dyn Emitter> = Box::new(ErrorEmitter {
179        diagnostics: diagnostics.clone(),
180        cm: cm.clone(),
181        opts: config,
182    });
183
184    let handler = Handler::with_emitter(true, false, emitter);
185
186    let ret = HANDLER.set(&handler, || op(&handler));
187
188    match ret {
189        Ok(ret) => {
190            if diagnostics.contains_error() {
191                Err(diagnostics.as_array(cm.clone(), config))
192            } else {
193                Ok(ret)
194            }
195        }
196        Err(err) => Err(diagnostics.as_array_with_anyhow(err, cm.clone(), config)),
197    }
198}