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 diagnostics = self
91            .0
92            .lock()
93            .expect("Failed to access the diagnostics lock");
94
95        // If there are no diagnostics, return empty vector without initializing handler
96        if diagnostics.is_empty() {
97            return Vec::new();
98        }
99
100        let handler = to_pretty_handler(color);
101        diagnostics
102            .iter()
103            .map(|d| d.to_pretty_string(cm, skip_filename, &handler))
104            .collect::<Vec<String>>()
105    }
106
107    /// Determines if the diagnostics collection includes any errors.
108    ///
109    /// Iterates through the diagnostics to find any with a level of `Error`,
110    /// returning `true` if found.
111    pub fn contains_error(&self) -> bool {
112        self.0
113            .lock()
114            .expect("Failed to access the diagnostics lock")
115            .iter()
116            .any(|d| d.level == swc_common::errors::Level::Error)
117    }
118}
119
120#[derive(Debug, Clone, Copy)]
121pub struct HandlerOpts {
122    /// [ColorConfig::Auto] is the default, and it will print colors unless the
123    /// environment variable `NO_COLOR` is not 1.
124    pub color: ColorConfig,
125
126    /// Defaults to `false`.
127    pub skip_filename: bool,
128}
129
130impl Default for HandlerOpts {
131    fn default() -> Self {
132        Self {
133            color: ColorConfig::Auto,
134            skip_filename: false,
135        }
136    }
137}
138
139pub fn to_pretty_handler(color: ColorConfig) -> GraphicalReportHandler {
140    match color {
141        ColorConfig::Auto => {
142            if cfg!(target_arch = "wasm32") {
143                return to_pretty_handler(ColorConfig::Always).with_context_lines(3);
144            }
145
146            static ENABLE: Lazy<bool> =
147                Lazy::new(|| !env::var("NO_COLOR").map(|s| s == "1").unwrap_or(false));
148
149            if *ENABLE {
150                to_pretty_handler(ColorConfig::Always)
151            } else {
152                to_pretty_handler(ColorConfig::Never)
153            }
154        }
155        ColorConfig::Always => GraphicalReportHandler::default(),
156        ColorConfig::Never => GraphicalReportHandler::default().with_theme(GraphicalTheme::none()),
157    }
158    .with_context_lines(3)
159    .with_wrap_lines(false)
160    .with_break_words(false)
161}
162
163/// Try operation with a [Handler] and prints the errors as a [String] wrapped
164/// by [Err].
165pub fn try_with_handler<F, Ret>(
166    cm: Lrc<SourceMap>,
167    config: HandlerOpts,
168    op: F,
169) -> Result<Ret, TWithDiagnosticArray<anyhow::Error>>
170where
171    F: FnOnce(&Handler) -> Result<Ret, anyhow::Error>,
172{
173    try_with_handler_inner(cm, config, op)
174}
175
176fn try_with_handler_inner<F, Ret>(
177    cm: Lrc<SourceMap>,
178    config: HandlerOpts,
179    op: F,
180) -> Result<Ret, TWithDiagnosticArray<anyhow::Error>>
181where
182    F: FnOnce(&Handler) -> Result<Ret, anyhow::Error>,
183{
184    let diagnostics = ThreadSafetyDiagnostics::default();
185
186    let emitter: Box<dyn Emitter> = Box::new(ErrorEmitter {
187        diagnostics: diagnostics.clone(),
188        cm: cm.clone(),
189        opts: config,
190    });
191
192    let handler = Handler::with_emitter(true, false, emitter);
193
194    let ret = HANDLER.set(&handler, || op(&handler));
195
196    match ret {
197        Ok(ret) => {
198            if diagnostics.contains_error() {
199                Err(diagnostics.as_array(cm.clone(), config))
200            } else {
201                Ok(ret)
202            }
203        }
204        Err(err) => Err(diagnostics.as_array_with_anyhow(err, cm.clone(), config)),
205    }
206}