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) -> 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                issue.severity,
32                issue.source.ident(),
33                Vc::cell(issue.title),
34                issue.message.cell(),
35                issue.code,
36                issue.issue_source,
37            )
38            .to_resolved()
39            .await?
40            .emit();
41        }
42        Ok(())
43    }
44
45    pub fn last_emitted_issue(&self) -> Option<Vc<AnalyzeIssue>> {
46        let inner = self.inner.lock();
47        inner.emitted_issues.last().map(|issue| {
48            AnalyzeIssue::new(
49                issue.severity,
50                issue.source.ident(),
51                Vc::cell(issue.title.clone()),
52                issue.message.clone().cell(),
53                issue.code.clone(),
54                issue.issue_source.clone(),
55            )
56        })
57    }
58}
59
60struct IssueCollectorInner {
61    emitted_issues: Vec<PlainAnalyzeIssue>,
62}
63struct PlainAnalyzeIssue {
64    severity: IssueSeverity,
65    source: ResolvedVc<Box<dyn Source>>,
66    title: RcStr,
67    message: StyledString,
68    code: Option<RcStr>,
69    issue_source: Option<IssueSource>,
70}
71
72pub struct IssueEmitter {
73    pub source: ResolvedVc<Box<dyn Source>>,
74    pub source_map: Arc<SourceMap>,
75    pub title: Option<RcStr>,
76    inner: Arc<Mutex<IssueCollectorInner>>,
77}
78
79impl IssueEmitter {
80    pub fn new(
81        source: ResolvedVc<Box<dyn Source>>,
82        source_map: Arc<SourceMap>,
83        title: Option<RcStr>,
84    ) -> (Self, IssueCollector) {
85        let inner = Arc::new(Mutex::new(IssueCollectorInner {
86            emitted_issues: vec![],
87        }));
88        (
89            Self {
90                source,
91                source_map,
92                title,
93                inner: inner.clone(),
94            },
95            IssueCollector { inner },
96        )
97    }
98}
99
100impl Emitter for IssueEmitter {
101    fn emit(&mut self, db: &mut DiagnosticBuilder<'_>) {
102        let db = db.take();
103        let level = db.level;
104        let mut message = db
105            .message
106            .iter()
107            .map(|s| s.0.as_ref())
108            .collect::<Vec<_>>()
109            .join("");
110        let code = db.code.as_ref().map(|d| match d {
111            DiagnosticId::Error(s) => format!("error {s}").into(),
112            DiagnosticId::Lint(s) => format!("lint {s}").into(),
113        });
114        let is_lint = db
115            .code
116            .as_ref()
117            .is_some_and(|d| matches!(d, DiagnosticId::Lint(_)));
118
119        let severity = if is_lint {
120            IssueSeverity::Suggestion
121        } else {
122            match level {
123                Level::Bug => IssueSeverity::Bug,
124                Level::Fatal | Level::PhaseFatal => IssueSeverity::Fatal,
125                Level::Error => IssueSeverity::Error,
126                Level::Warning => IssueSeverity::Warning,
127                Level::Note => IssueSeverity::Note,
128                Level::Help => IssueSeverity::Hint,
129                Level::Cancelled => IssueSeverity::Error,
130                Level::FailureNote => IssueSeverity::Note,
131            }
132        };
133
134        let title;
135        if let Some(t) = self.title.as_ref() {
136            title = t.clone();
137        } else {
138            let mut message_split = message.split('\n');
139            title = message_split.next().unwrap().to_string().into();
140            message = message_split.remainder().unwrap_or("").to_string();
141        }
142
143        let source = db.span.primary_span().map(|span| {
144            IssueSource::from_swc_offsets(self.source, span.lo.to_u32(), span.hi.to_u32())
145        });
146        // TODO add other primary and secondary spans with labels as sub_issues
147
148        // This can be invoked by swc on different threads, so we cannot call any turbo-tasks or
149        // create cells here.
150        let issue = PlainAnalyzeIssue {
151            severity,
152            source: self.source,
153            title,
154            message: StyledString::Text(message.into()),
155            code,
156            issue_source: source,
157        };
158
159        let mut inner = self.inner.lock();
160        inner.emitted_issues.push(issue);
161    }
162}