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