turbopack_swc_utils/
emitter.rs1use 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 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}