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 code = db.code.as_ref().map(|d| match d {
115            DiagnosticId::Error(s) => format!("error {s}").into(),
116            DiagnosticId::Lint(s) => format!("lint {s}").into(),
117        });
118        let is_lint = db
119            .code
120            .as_ref()
121            .is_some_and(|d| matches!(d, DiagnosticId::Lint(_)));
122
123        let severity = if is_lint {
124            IssueSeverity::Suggestion
125        } else {
126            match level {
127                Level::Bug => IssueSeverity::Bug,
128                Level::Fatal | Level::PhaseFatal => IssueSeverity::Fatal,
129                Level::Error => IssueSeverity::Error,
130                Level::Warning => IssueSeverity::Warning,
131                Level::Note => IssueSeverity::Note,
132                Level::Help => IssueSeverity::Hint,
133                Level::Cancelled => IssueSeverity::Error,
134                Level::FailureNote => IssueSeverity::Note,
135            }
136        };
137
138        let title;
139        if let Some(t) = self.title.as_ref() {
140            title = t.clone();
141        } else {
142            let mut message_split = message.split('\n');
143            title = message_split.next().unwrap().to_string().into();
144            message = message_split.remainder().unwrap_or("").to_string();
145        }
146
147        let source = db.span.primary_span().map(|span| {
148            IssueSource::from_swc_offsets(self.source, span.lo.to_u32(), span.hi.to_u32())
149        });
150        // TODO add other primary and secondary spans with labels as sub_issues
151
152        // This can be invoked by swc on different threads, so we cannot call any turbo-tasks or
153        // create cells here.
154        let issue = PlainAnalyzeIssue {
155            severity,
156            source: self.source,
157            title,
158            message: StyledString::Text(message.into()),
159            code,
160            issue_source: source,
161        };
162
163        let mut inner = self.inner.lock();
164        inner.emitted_issues.push(issue);
165    }
166}