Skip to main content

turbopack_swc_utils/
emitter.rs

1use std::{mem::take, sync::Arc};
2
3use anyhow::Result;
4use parking_lot::Mutex;
5use swc_core::common::{
6    SourceMap,
7    errors::{DiagnosticBuilder, DiagnosticId, Emitter, Level},
8    source_map::SmallPos,
9};
10use turbo_rcstr::RcStr;
11use turbo_tasks::{ResolvedVc, Vc};
12use turbopack_core::{
13    issue::{IssueExt, IssueSeverity, IssueSource, StyledString, analyze::AnalyzeIssue},
14    source::Source,
15};
16
17#[must_use]
18pub struct IssueCollector {
19    inner: Arc<Mutex<IssueCollectorInner>>,
20}
21
22impl IssueCollector {
23    pub async fn emit(self, loose_errors: bool) -> Result<()> {
24        let issues = {
25            let mut inner = self.inner.lock();
26            take(&mut inner.emitted_issues)
27        };
28
29        for issue in issues {
30            AnalyzeIssue::new(
31                if loose_errors && issue.severity <= IssueSeverity::Error {
32                    IssueSeverity::Warning
33                } else {
34                    issue.severity
35                },
36                issue.source.ident(),
37                Vc::cell(issue.title),
38                issue.message.cell(),
39                issue.code,
40                issue.issue_source,
41            )
42            .to_resolved()
43            .await?
44            .emit();
45        }
46        Ok(())
47    }
48
49    pub fn last_emitted_issue(&self) -> Option<Vc<AnalyzeIssue>> {
50        let inner = self.inner.lock();
51        inner.emitted_issues.last().map(|issue| {
52            AnalyzeIssue::new(
53                issue.severity,
54                issue.source.ident(),
55                Vc::cell(issue.title.clone()),
56                issue.message.clone().cell(),
57                issue.code.clone(),
58                issue.issue_source,
59            )
60        })
61    }
62}
63
64struct IssueCollectorInner {
65    emitted_issues: Vec<PlainAnalyzeIssue>,
66}
67struct PlainAnalyzeIssue {
68    severity: IssueSeverity,
69    source: ResolvedVc<Box<dyn Source>>,
70    title: RcStr,
71    message: StyledString,
72    code: Option<RcStr>,
73    issue_source: Option<IssueSource>,
74}
75
76pub struct IssueEmitter {
77    pub source: ResolvedVc<Box<dyn Source>>,
78    pub source_map: Arc<SourceMap>,
79    pub title: Option<RcStr>,
80    inner: Arc<Mutex<IssueCollectorInner>>,
81}
82
83impl IssueEmitter {
84    pub fn new(
85        source: ResolvedVc<Box<dyn Source>>,
86        source_map: Arc<SourceMap>,
87        title: Option<RcStr>,
88    ) -> (Self, IssueCollector) {
89        let inner = Arc::new(Mutex::new(IssueCollectorInner {
90            emitted_issues: vec![],
91        }));
92        (
93            Self {
94                source,
95                source_map,
96                title,
97                inner: inner.clone(),
98            },
99            IssueCollector { inner },
100        )
101    }
102}
103
104impl Emitter for IssueEmitter {
105    fn emit(&mut self, db: &mut DiagnosticBuilder<'_>) {
106        let db = db.take();
107        let level = db.level;
108        let mut message = db
109            .message
110            .iter()
111            .map(|s| s.0.as_ref())
112            .collect::<Vec<_>>()
113            .join("");
114        let is_lint = db
115            .code
116            .as_ref()
117            .is_some_and(|d| matches!(d, DiagnosticId::Lint(_)));
118
119        let code = db.code.map(|d| match d {
120            DiagnosticId::Error(s) => s.into(),
121            DiagnosticId::Lint(s) => format!("lint {s}").into(),
122        });
123
124        let severity = if is_lint {
125            IssueSeverity::Suggestion
126        } else {
127            match level {
128                Level::Bug => IssueSeverity::Bug,
129                Level::Fatal | Level::PhaseFatal => IssueSeverity::Fatal,
130                Level::Error => IssueSeverity::Error,
131                Level::Warning => IssueSeverity::Warning,
132                Level::Note => IssueSeverity::Note,
133                Level::Help => IssueSeverity::Hint,
134                Level::Cancelled => IssueSeverity::Error,
135                Level::FailureNote => IssueSeverity::Note,
136            }
137        };
138
139        // When self.title is set (e.g. "Parsing ecmascript source code failed"),
140        // use the SWC diagnostic as the title for a more specific error message,
141        // and demote the generic title to the description.
142        // When self.title is not set, use the first line of the message as title
143        // and the rest as description.
144        let title;
145        if let Some(t) = self.title.as_ref() {
146            title = message.trim().into();
147            message = t.to_string();
148        } else {
149            let mut message_split = message.split('\n');
150            title = message_split.next().unwrap().trim().to_string().into();
151            message = message_split.remainder().unwrap_or("").trim().to_string();
152        }
153
154        let source = db.span.primary_span().map(|span| {
155            IssueSource::from_swc_offsets(self.source, span.lo.to_u32(), span.hi.to_u32())
156        });
157        // TODO add other primary and secondary spans with labels as sub_issues
158
159        // This can be invoked by swc on different threads, so we cannot call any turbo-tasks or
160        // create cells here.
161        let issue = PlainAnalyzeIssue {
162            severity,
163            source: self.source,
164            title,
165            message: StyledString::Text(message.into()),
166            code,
167            issue_source: source,
168        };
169
170        let mut inner = self.inner.lock();
171        inner.emitted_issues.push(issue);
172    }
173}