Skip to content

Commit 0df7054

Browse files
committed
Attach import traces to Issues
The protocol is: * when building the modulegraph collect all issues for each module * after building the modulegraph, compute the shortest path from each issue to an entry point module * when collecting issues into `PlainIssues` attach the paths when possible. Downsides of this approach: * need a `strongly_consistent_read` during graph construction. * no clear approach to attach import traces for `Issues` that are not reported during graph construction. Alternatives to pursue: * make `module-ident` an optional feature of the `Issue` trait and try to push it down * move all path computation into `get_plain_issues` by making the module_graph itself be a collectible.
1 parent 5136f8e commit 0df7054

File tree

27 files changed

+294
-143
lines changed

27 files changed

+294
-143
lines changed

crates/next-core/src/next_client_reference/visit_client_reference.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ impl Visit<VisitClientReferenceNode> for VisitClientReference {
322322
};
323323

324324
let referenced_modules =
325-
primary_chunkable_referenced_modules(*parent_module, include_traced).await?;
325+
primary_chunkable_referenced_modules(parent_module, include_traced)
326+
.connect()
327+
.await?;
326328

327329
let referenced_modules = referenced_modules
328330
.iter()

turbopack/crates/turbopack-cli-utils/src/issue.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use anyhow::{Result, anyhow};
1212
use crossterm::style::{StyledContent, Stylize};
1313
use owo_colors::{OwoColorize as _, Style};
1414
use rustc_hash::{FxHashMap, FxHashSet};
15-
use turbo_tasks::{RawVc, ReadRef, TransientInstance, TransientValue, TryJoinIterExt, Vc};
15+
use turbo_tasks::{RawVc, ReadRef, TransientInstance, TransientValue, Vc};
1616
use turbo_tasks_fs::{FileLinesContent, source_context::get_source_context};
1717
use turbopack_core::issue::{
18-
CapturedIssues, Issue, IssueReporter, IssueSeverity, PlainIssue, PlainIssueProcessingPathItem,
18+
CapturedIssues, IssueReporter, IssueSeverity, PlainIssue, PlainIssueProcessingPathItem,
1919
PlainIssueSource, StyledString,
2020
};
2121

@@ -141,7 +141,7 @@ pub fn format_issue(
141141
.replace("[project]", &current_dir.to_string_lossy())
142142
.replace("/./", "/")
143143
.replace("\\\\?\\", "");
144-
let stgae = plain_issue.stage.to_string();
144+
let stage = plain_issue.stage.to_string();
145145

146146
let mut styled_issue = style_issue_source(plain_issue, &context_path);
147147
let description = &plain_issue.description;
@@ -170,12 +170,18 @@ pub fn format_issue(
170170
writeln!(styled_issue, "{path}").unwrap();
171171
}
172172
}
173+
if let Some(trace) = &plain_issue.import_trace {
174+
writeln!(styled_issue, "Import trace for requested module:").unwrap();
175+
for line in trace {
176+
writeln!(styled_issue, " {line}").unwrap();
177+
}
178+
}
173179

174180
write!(
175181
issue_text,
176182
"{} - [{}] {}",
177183
severity.style(severity_to_style(severity)),
178-
stgae,
184+
stage,
179185
plain_issue.file_path
180186
)
181187
.unwrap();
@@ -354,15 +360,14 @@ impl IssueReporter for ConsoleUi {
354360
} = self.options;
355361
let mut grouped_issues: GroupedIssues = FxHashMap::default();
356362

357-
let issues = issues
358-
.iter_with_shortest_path()
359-
.map(|(issue, path)| async move {
360-
let plain_issue = issue.into_plain(path);
361-
let id = plain_issue.internal_hash(false).await?;
362-
Ok((plain_issue.await?, *id))
363+
let plain_issues = issues.get_plain_issues().await?;
364+
let issues = plain_issues
365+
.iter()
366+
.map(|plain_issue| {
367+
let id = plain_issue.internal_hash_ref(false);
368+
(plain_issue, id)
363369
})
364-
.try_join()
365-
.await?;
370+
.collect::<Vec<_>>();
366371

367372
let issue_ids = issues.iter().map(|(_, id)| *id).collect::<FxHashSet<_>>();
368373
let mut new_ids = self
@@ -390,7 +395,7 @@ impl IssueReporter for ConsoleUi {
390395
let category_map = severity_map.entry(stage.clone()).or_default();
391396
let issues = category_map.entry(context_path.to_string()).or_default();
392397

393-
let mut styled_issue = style_issue_source(&plain_issue, &context_path);
398+
let mut styled_issue = style_issue_source(plain_issue, &context_path);
394399
let description = &plain_issue.description;
395400
if let Some(description) = description {
396401
writeln!(

turbopack/crates/turbopack-core/src/issue/mod.rs

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{
1212
use anyhow::{Result, anyhow};
1313
use async_trait::async_trait;
1414
use auto_hash_map::AutoSet;
15+
use rustc_hash::FxHashMap;
1516
use serde::{Deserialize, Serialize};
1617
use turbo_rcstr::RcStr;
1718
use turbo_tasks::{
@@ -154,6 +155,7 @@ pub trait Issue {
154155

155156
async fn into_plain(
156157
self: Vc<Self>,
158+
import_trace: Option<ResolvedVc<ImportTrace>>,
157159
processing_path: Vc<OptionIssueProcessingPathItems>,
158160
) -> Result<Vc<PlainIssue>> {
159161
let description = match *self.description().await? {
@@ -180,21 +182,47 @@ pub trait Issue {
180182
None
181183
}
182184
},
185+
// delete sub_issues?
183186
sub_issues: self
184187
.sub_issues()
185188
.await?
186189
.iter()
187190
.map(|i| async move {
188-
anyhow::Ok(i.into_plain(OptionIssueProcessingPathItems::none()).await?)
191+
anyhow::Ok(
192+
i.into_plain(None, OptionIssueProcessingPathItems::none())
193+
.await?,
194+
)
189195
})
190196
.try_join()
191197
.await?,
192198
processing_path: processing_path.into_plain().await?,
199+
import_trace: if let Some(s) = import_trace {
200+
Some(s.await?)
201+
} else {
202+
None
203+
},
193204
}
194205
.cell())
195206
}
196207
}
197208

209+
// A collectible trait wrapper for `ImportTraceForIssues`
210+
// To access data simply downcast to `ImportTraceForIssues`
211+
#[turbo_tasks::value_trait]
212+
pub trait ImportTraceForIssuesTrait {}
213+
214+
// An association between an import trace and a collection of issues reported for a given module.
215+
// Used to augment issue reporting.
216+
//
217+
#[turbo_tasks::value(shared)]
218+
pub struct ImportTraceForIssues {
219+
pub path: ResolvedVc<ImportTrace>,
220+
pub issues: Vec<ResolvedVc<Box<dyn Issue>>>,
221+
}
222+
223+
#[turbo_tasks::value_impl]
224+
impl ImportTraceForIssuesTrait for ImportTraceForIssues {}
225+
198226
#[turbo_tasks::value_trait]
199227
trait IssueProcessingPath {
200228
fn shortest_path(
@@ -370,6 +398,7 @@ pub struct CapturedIssues {
370398
issues: AutoSet<ResolvedVc<Box<dyn Issue>>>,
371399
#[cfg(feature = "issue_path")]
372400
processing_path: ResolvedVc<ItemIssueProcessingPath>,
401+
import_trace: AutoSet<ResolvedVc<Box<dyn ImportTraceForIssuesTrait>>>,
373402
}
374403

375404
#[turbo_tasks::value_impl]
@@ -397,38 +426,38 @@ impl CapturedIssues {
397426
self.issues.iter().copied()
398427
}
399428

400-
/// Returns an iterator over the issues with the shortest path from the root
401-
/// issue to each issue.
402-
pub fn iter_with_shortest_path(
403-
&self,
404-
) -> impl Iterator<
405-
Item = (
406-
ResolvedVc<Box<dyn Issue>>,
407-
Vc<OptionIssueProcessingPathItems>,
408-
),
409-
> + '_ {
410-
self.issues.iter().map(|issue| {
411-
#[cfg(feature = "issue_path")]
412-
let path = self.processing_path.shortest_path(**issue);
413-
#[cfg(not(feature = "issue_path"))]
414-
let path = OptionIssueProcessingPathItems::none();
415-
(*issue, path)
416-
})
417-
}
418-
429+
// Returns all the issues as formatted `PlainIssues`.
419430
pub async fn get_plain_issues(&self) -> Result<Vec<ReadRef<PlainIssue>>> {
431+
let issue_to_trace = self
432+
.import_trace
433+
.iter()
434+
.map(|trace| async move {
435+
ResolvedVc::try_downcast_type::<ImportTraceForIssues>(*trace)
436+
.unwrap()
437+
.await
438+
})
439+
.try_join()
440+
.await?
441+
.iter()
442+
.flat_map(|trace| trace.issues.iter().map(|issue| (*issue, *trace.path)))
443+
.collect::<FxHashMap<_, _>>();
444+
420445
let mut list = self
421446
.issues
422447
.iter()
423-
.map(|&issue| async move {
424-
#[cfg(feature = "issue_path")]
425-
return issue
426-
.into_plain(self.processing_path.shortest_path(*issue))
427-
.await;
428-
#[cfg(not(feature = "issue_path"))]
429-
return issue
430-
.into_plain(OptionIssueProcessingPathItems::none())
431-
.await;
448+
.map(|&issue| {
449+
let issue_to_trace = &issue_to_trace;
450+
async move {
451+
let import_trace = issue_to_trace.get(&issue).cloned();
452+
#[cfg(feature = "issue_path")]
453+
return issue
454+
.into_plain(import_trace, self.processing_path.shortest_path(*issue))
455+
.await;
456+
#[cfg(not(feature = "issue_path"))]
457+
return issue
458+
.into_plain(import_trace, OptionIssueProcessingPathItems::none())
459+
.await;
460+
}
432461
})
433462
.try_join()
434463
.await?;
@@ -635,6 +664,9 @@ pub struct OptionIssueSource(Option<IssueSource>);
635664
#[turbo_tasks::value(transparent)]
636665
pub struct OptionStyledString(Option<ResolvedVc<StyledString>>);
637666

667+
#[turbo_tasks::value(transparent, shared)]
668+
pub struct ImportTrace(pub Vec<RcStr>);
669+
638670
#[turbo_tasks::value(shared, serialization = "none")]
639671
#[derive(Clone, Debug, PartialOrd, Ord, DeterministicHash, Serialize)]
640672
pub enum IssueStage {
@@ -693,6 +725,7 @@ pub struct PlainIssue {
693725
pub source: Option<PlainIssueSource>,
694726
pub sub_issues: Vec<ReadRef<PlainIssue>>,
695727
pub processing_path: ReadRef<PlainIssueProcessingPath>,
728+
pub import_trace: Option<ReadRef<ImportTrace>>,
696729
}
697730

698731
fn hash_plain_issue(issue: &PlainIssue, hasher: &mut Xxh3Hash64Hasher, full: bool) {
@@ -739,22 +772,6 @@ impl PlainIssue {
739772
}
740773
}
741774

742-
#[turbo_tasks::value_impl]
743-
impl PlainIssue {
744-
/// We need deduplicate issues that can come from unique paths, but
745-
/// represent the same underlying problem. Eg, a parse error for a file
746-
/// that is compiled in both client and server contexts.
747-
///
748-
/// Passing [full] will also hash any sub-issues and processing paths. While
749-
/// useful for generating exact matching hashes, it's possible for the
750-
/// same issue to pass from multiple processing paths, making for overly
751-
/// verbose logging.
752-
#[turbo_tasks::function]
753-
pub fn internal_hash(&self, full: bool) -> Vc<u64> {
754-
Vc::cell(self.internal_hash_ref(full))
755-
}
756-
}
757-
758775
#[turbo_tasks::value(serialization = "none")]
759776
#[derive(Clone, Debug, PartialOrd, Ord)]
760777
pub struct PlainIssueSource {
@@ -961,6 +978,7 @@ where
961978
None,
962979
self.peek_collectibles(),
963980
)),
981+
import_trace: self.peek_collectibles(),
964982
})
965983
}
966984

@@ -972,6 +990,7 @@ where
972990
None,
973991
self.take_collectibles(),
974992
)),
993+
import_trace: self.take_collectibles(),
975994
})
976995
}
977996
}

0 commit comments

Comments
 (0)