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, 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 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 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}